diff --git a/README-service.md b/README-service.md
new file mode 100644
index 0000000000000000000000000000000000000000..8125190b241ecc9c517b6b4e2ace2a7ab325a0e0
--- /dev/null
+++ b/README-service.md
@@ -0,0 +1,319 @@
+Service module
+==============
+
+Description
+-----------
+
+The service module allows to ensure presence and absence of services.
+
+
+Features
+--------
+
+* Service management
+
+
+Supported FreeIPA Versions
+--------------------------
+
+FreeIPA versions 4.4.0 and up are supported by the ipaservice module.
+
+Option `skip_host_check` requires FreeIPA version 4.7.0 or later.
+
+
+Requirements
+------------
+
+**Controller**
+* Ansible version: 2.8+
+
+**Node**
+* Supported FReeIPA version (see above)
+
+
+Usage
+=====
+
+Example inventory file
+
+```ini
+[ipaserver]
+ipaserver.test.local
+```
+
+
+Example playbook to make sure service is present:
+
+```yaml
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure service is present
+  - ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/www.example.com
+      certificate:
+        - MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQELBQAw
+        DzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQxMDhaMA8xDT
+        ALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+XVVGFYpH
+        VkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJzM
+        LJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmjfMXp0nIT
+        oTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn0vQINt55YoEd1s
+        4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpc
+        xj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1
+        UdDgQWBBRV0j7JYukuH/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+Q
+        eNlRLXDlEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs
+        5y1Zp0WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqic
+        uPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkGwIt1OH
+        2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyEiaio3693l6no
+        obyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj9uKQ5GTWRh59VnRBVC
+        /SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV7MVq
+      pac_type: PAD
+      auth_ind: otp
+      requires_pre_auth: false
+      ok_as_delegate: false
+      ok_to_auth_as_delegate: false
+      skip-host-check: true
+      force: true
+```
+
+
+Example playbook to make sure service is absent:
+
+```yaml
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure service is present
+  - ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/www.example.com
+      state: absent
+```
+
+
+Example playbook to make sure service is disabled:
+
+```yaml
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure service is present
+  - ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/www.example.com
+      state: disabled
+```
+
+Example playbook to add a service even if the host object does not exist, but only if it does have a DNS entry:
+
+```yaml
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure service is present
+  - ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/www.example.com
+      skip_host_check: true
+      force: false
+```
+
+Example playbook to add a service if it does have a DNS entry, but host object exits:
+
+```yaml
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure service is present
+  - ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/www.example.com
+      skip_host_check: false
+      force: true
+```
+
+Example playbook to ensure service has a certificate:
+
+```yaml
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure service member certificate is present.
+  - ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/www.example.com
+      certificate:
+        - MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQELBQAw
+        DzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQxMDhaMA8xDT
+        ALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+XVVGFYpH
+        VkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJzM
+        LJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmjfMXp0nIT
+        oTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn0vQINt55YoEd1s
+        4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpc
+        xj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1
+        UdDgQWBBRV0j7JYukuH/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+Q
+        eNlRLXDlEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs
+        5y1Zp0WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqic
+        uPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkGwIt1OH
+        2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyEiaio3693l6no
+        obyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj9uKQ5GTWRh59VnRBVC
+        /SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV7MVq
+      action: member
+      state: present
+```
+
+Example playbook to add a principal to the service:
+
+```yaml
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+    # Principal host/test.example.com present in service.
+    - ipaservice:
+        ipaadmin_password: SomeADMINpassword
+        name: HTTP/www.example.com
+        principal: host/principal.example.com
+        action: member
+```
+
+Example playbook to enable a host to manage service:
+
+```yaml
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+    # Ensure host can manage service, again.
+    - ipaservice:
+        ipaadmin_password: SomeADMINpassword
+        name: HTTP/www.example.com
+        host: host1.example.com
+        action: member
+```
+
+Example playbook to allow users, groups, hosts or hostgroups to create a keytab of this service:
+
+```yaml
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+    # Allow users, groups, hosts or host groups to create a keytab of this service.
+    - ipaservice:
+        ipaadmin_password: SomeADMINpassword
+        name: HTTP/www.example.com
+        allow_create_keytab_user:
+        - user01
+        - user02
+        allow_create_keytab_group:
+        - group01
+        - group02
+        allow_create_keytab_host:
+        - host1.example.com
+        - host2.example.com
+        allow_create_keytab_hostgroup:
+        - hostgroup01
+        - hostgroup02
+        action: member
+```
+
+Example playbook to allow users, groups, hosts or hostgroups to retrieve a keytab of this service:
+
+```yaml
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+    # Allow users, groups, hosts or host groups to retrieve a keytab of this service.
+    - ipaservice:
+        ipaadmin_password: SomeADMINpassword
+        name: HTTP/www.example.com
+        allow_retrieve_keytab_user:
+        - user01
+        - user02
+        allow_retrieve_keytab_group:
+        - group01
+        - group02
+        allow_retrieve_keytab_host:
+        - "{{ host1_fqdn }}"
+        - "{{ host2_fqdn }}"
+        allow_retrieve_keytab_hostgroup:
+        - hostgroup01
+        - hostgroup02
+        action: member
+```
+
+
+Variables
+---------
+
+ipaservice
+
+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
+`name` \| `service` | The list of service name strings. | yes
+`certificate` \| `usercertificate` | Base-64 encoded service certificate. | no
+`pac_type` \| `ipakrbauthzdata` | Supported PAC type. It can be one of `MS-PAC`, `PAD`, or `NONE`. | no
+`auth_ind` \| `krbprincipalauthind` | Defines a whitelist for Authentication Indicators. It can be any of `otp`, `radius`, `pkinit`, or `hardened`. | no
+`requires_pre_auth` \| `ipakrbrequirespreauth` | Pre-authentication is required for the service. Default to true. (bool) | no
+`ok_as_delegate` \|  `ipakrbokasdelegate` | Client credentials may be delegated to the service. Default to false. (bool) | no
+`ok_to_auth_as_delegate` \|  `ipakrboktoauthasdelegate` | The service is allowed to authenticate on behalf of a client. Default to false. (bool) | no
+`skip_host_check` | Force service to be created even when host object does not exist to manage it. Default to false. (bool)| no
+`force` | Force principal name even if host not in DNS. Default to false. (bool) | no
+`host` \| `managedby_host`| Hosts that can manage the service. | no
+`allow_create_keytab_user` \| `ipaallowedtoperform_write_keys_user` | Users allowed to create a keytab of this host. | no
+`allow_create_keytab_group` \| `ipaallowedtoperform_write_keys_group`| Groups allowed to create a keytab of this host. | no
+`allow_create_keytab_host` \| `ipaallowedtoperform_write_keys_host`| Hosts allowed to create a keytab of this host. | no
+`allow_create_keytab_hostgroup` \| `ipaallowedtoperform_write_keys_group`| Host groups allowed to create a keytab of this host. | no
+`allow_retrieve_keytab_user` \| `ipaallowedtoperform_read_keys_user` | Users allowed to retrieve a keytab of this host. | no
+`allow_retrieve_keytab_group` \| `ipaallowedtoperform_read_keys_group` | Groups allowed to retrieve a keytab of this host. | no
+`allow_retrieve_keytab_host` \| `ipaallowedtoperform_read_keys_host` | Hosts allowed to retrieve a keytab from of host. | no
+`allow_retrieve_keytab_hostgroup` \| `ipaallowedtoperform_read_keys_hostgroup` | Host groups allowed to retrieve a keytab of this host. | no
+`action` | Work on service or member level. It can be on of `member` or `service` and defaults to `service`. | no
+`state` | The state to ensure. It can be one of `present`, `absent`, or `disabled`, default: `present`. | no
+
+
+Authors
+=======
+
+Rafael Jeffman
diff --git a/README.md b/README.md
index eea8026675459e4a35e81213cee5e558d85a8506..f5d4c257b4f478afe4a3ee9b99b639f8bc2b73b0 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@ Features
 * Modules for host management
 * Modules for hostgroup management
 * Modules for pwpolicy management
+* Modules for service management
 * Modules for sudocmd management
 * Modules for sudocmdgroup management
 * Modules for sudorule management
@@ -413,6 +414,7 @@ Modules in plugin/modules
 * [ipahost](README-host.md)
 * [ipahostgroup](README-hostgroup.md)
 * [ipapwpolicy](README-pwpolicy.md)
+* [ipaservice](README-service.md)
 * [ipasudocmd](README-sudocmd.md)
 * [ipasudocmdgroup](README-sudocmdgroup.md)
 * [ipasudorule](README-sudorule.md)
diff --git a/playbooks/service/service-host-is-absent.yml b/playbooks/service/service-host-is-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5963340f3447aa83c8569fb905381cc7d4b84e7c
--- /dev/null
+++ b/playbooks/service/service-host-is-absent.yml
@@ -0,0 +1,14 @@
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure management host is absent.
+  - ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/www.example.com
+      host: "{{ groups.ipaserver[0] }}"
+      action: member
+      state: absent
diff --git a/playbooks/service/service-host-is-present.yml b/playbooks/service/service-host-is-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2460051ebf793ab6e357cf24cd02499c1aaeb777
--- /dev/null
+++ b/playbooks/service/service-host-is-present.yml
@@ -0,0 +1,13 @@
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure management host is present.
+  - ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/www.example.com
+      host: "{{ groups.ipaserver[0] }}"
+      action: member
diff --git a/playbooks/service/service-is-absent.yml b/playbooks/service/service-is-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fe65771ef893048fcc888adb4f0ca226026acaac
--- /dev/null
+++ b/playbooks/service/service-is-absent.yml
@@ -0,0 +1,12 @@
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure service is absent
+  - ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/www.example.com
+      state: absent
diff --git a/playbooks/service/service-is-disabled.yml b/playbooks/service/service-is-disabled.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2bf01fb156aa88ba0a3f9d8ded7ea084ccc06f9d
--- /dev/null
+++ b/playbooks/service/service-is-disabled.yml
@@ -0,0 +1,12 @@
+---
+- name: Playbook to disable IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure service is disabled
+  - ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/www.example.com
+      state: disabled
diff --git a/playbooks/service/service-is-present-with-all-attributes.yml b/playbooks/service/service-is-present-with-all-attributes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f7e59ebca7ad8082befb552d5b3e06ba433dab78
--- /dev/null
+++ b/playbooks/service/service-is-present-with-all-attributes.yml
@@ -0,0 +1,23 @@
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure service is present
+  - ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/www.example.com
+      certificate:
+        - MIICBjCCAW8CFHnm32VcXaUDGfEGdDL/erPSijUAMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwHhcNMjAwMTIzMDA1NjQ2WhcNMjEwMTIyMDA1NjQ2WjBCMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYrdVmsr7iT3f67DM5bb1osSEe5/c91UUMEIcFq5wrgBhzVfs8iIMDVC1yiUGTsDLJNJc4nb1tUxeR9K5fh25E6n/eWDBP75NStotjAXRU4Ahi3FNRhWFOKesds5xNqgDk5/dY8UekJv2yUblQuZzeF8b2XFrmHuCaYuFctzPfWwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBACF+5RS8Ce0HRixGPu4Xd51i+Kzblg++lx8fDJ8GW5G16/Z1AsB72Hc7etJL2PksHlue/xCq6SA9fIfHc4TBNCiWjPSP1NhHJeYyoPiSkcYsqXuxWyoyRLbnAhBVvhoiqZbUt3u3tGB0uMMA0yJvj07mP7Nea2KdBYVH8X1pM0V+
+      pac_type:
+        - MS-PAC
+        - PAD
+      auth_ind: otp
+      force: no
+      requires_pre_auth: yes
+      ok_as_delegate: no
+      ok_to_auth_as_delegate: no
+      action: service
+      state: present
diff --git a/playbooks/service/service-is-present-with-host-force.yml b/playbooks/service/service-is-present-with-host-force.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2268ea8f402afc815df7fe978177ee3d93f6c3eb
--- /dev/null
+++ b/playbooks/service/service-is-present-with-host-force.yml
@@ -0,0 +1,13 @@
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure service is present
+  - ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/ihavenodns.info
+      force: yes
+      # state: absent
diff --git a/playbooks/service/service-is-present-without-host-object.yml b/playbooks/service/service-is-present-without-host-object.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ddf72b8e24ad6a57e564fe2c4321487c77c2955c
--- /dev/null
+++ b/playbooks/service/service-is-present-without-host-object.yml
@@ -0,0 +1,12 @@
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure service is present
+  - ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/www.ansible.com
+      skip_host_check: yes
diff --git a/playbooks/service/service-is-present.yml b/playbooks/service/service-is-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..06e883431b13f6b3e275f7117387725eac68f703
--- /dev/null
+++ b/playbooks/service/service-is-present.yml
@@ -0,0 +1,11 @@
+---
+- name: Playbook to manage IPA service.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure service is present
+  - ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/www.example.com
diff --git a/playbooks/service/service-member-allow_create_keytab-absent.yml b/playbooks/service/service-member-allow_create_keytab-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d4a15ea4828753242d5527e52e2db4b7ebd5b15a
--- /dev/null
+++ b/playbooks/service/service-member-allow_create_keytab-absent.yml
@@ -0,0 +1,24 @@
+---
+- name: Service member allow_create_keytab absent
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Service HTTP/www.example.com members allow_create_keytab absent for users, groups, hosts and hostgroups
+    ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/www.example.com
+      allow_create_keytab_user:
+      - user01
+      - user02
+      allow_create_keytab_group:
+      - group01
+      - group02
+      allow_create_keytab_host:
+      - host01.example.com
+      - host02.example.com
+      allow_create_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+      state: absent
diff --git a/playbooks/service/service-member-allow_create_keytab-present.yml b/playbooks/service/service-member-allow_create_keytab-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b28b6dc23a89e061eaa267426543746c1938f2d4
--- /dev/null
+++ b/playbooks/service/service-member-allow_create_keytab-present.yml
@@ -0,0 +1,23 @@
+---
+- name: Service member allow_create_keytab present
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Service HTTP/www.example.com members allow_create_keytab present for users, groups, hosts and hostgroups
+    ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/www.example.com
+      allow_create_keytab_user:
+      - user01
+      - user02
+      allow_create_keytab_group:
+      - group01
+      - group02
+      allow_create_keytab_host:
+      - host01.example.com
+      - host02.example.com
+      allow_create_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
diff --git a/playbooks/service/service-member-allow_retrieve_keytab-absent.yml b/playbooks/service/service-member-allow_retrieve_keytab-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ceada70e5ee1b75e80f6fde05298fa28852902e2
--- /dev/null
+++ b/playbooks/service/service-member-allow_retrieve_keytab-absent.yml
@@ -0,0 +1,24 @@
+---
+- name: Service member allow_retrieve_keytab absent
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Service HTTP/www.example.com members allow_retrieve_keytab absent for users, groups, hosts and hostgroups
+    ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/www.example.com
+      allow_retrieve_keytab_user:
+      - user01
+      - user02
+      allow_retrieve_keytab_group:
+      - group01
+      - group02
+      allow_retrieve_keytab_host:
+      - host01.example.com
+      - host02.example.com
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+      state: absent
diff --git a/playbooks/service/service-member-allow_retrieve_keytab-present.yml b/playbooks/service/service-member-allow_retrieve_keytab-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ac98904b32b2ec3e58ab2d9366d76e7177cad28e
--- /dev/null
+++ b/playbooks/service/service-member-allow_retrieve_keytab-present.yml
@@ -0,0 +1,23 @@
+---
+- name: Service member allow_retrieve_keytab present
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Service HTTP/www.example.com members allow_retrieve_keytab present for users, groups, hosts and hostgroups
+    ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/www.example.com
+      allow_retrieve_keytab_user:
+      - user01
+      - user02
+      allow_retrieve_keytab_group:
+      - group01
+      - group02
+      allow_retrieve_keytab_host:
+      - host01.example.com
+      - host02.example.com
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
diff --git a/playbooks/service/service-member-certificate-absent.yml b/playbooks/service/service-member-certificate-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..57b71e5eda70c4cc8fffcf3cd93b0c314620ab12
--- /dev/null
+++ b/playbooks/service/service-member-certificate-absent.yml
@@ -0,0 +1,16 @@
+---
+- name: Service certificate absent.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure service certificate is absent
+  - ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/www.example.com
+
+      certificate:
+        - MIICBjCCAW8CFHnm32VcXaUDGfEGdDL/erPSijUAMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwHhcNMjAwMTIzMDA1NjQ2WhcNMjEwMTIyMDA1NjQ2WjBCMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYrdVmsr7iT3f67DM5bb1osSEe5/c91UUMEIcFq5wrgBhzVfs8iIMDVC1yiUGTsDLJNJc4nb1tUxeR9K5fh25E6n/eWDBP75NStotjAXRU4Ahi3FNRhWFOKesds5xNqgDk5/dY8UekJv2yUblQuZzeF8b2XFrmHuCaYuFctzPfWwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBACF+5RS8Ce0HRixGPu4Xd51i+Kzblg++lx8fDJ8GW5G16/Z1AsB72Hc7etJL2PksHlue/xCq6SA9fIfHc4TBNCiWjPSP1NhHJeYyoPiSkcYsqXuxWyoyRLbnAhBVvhoiqZbUt3u3tGB0uMMA0yJvj07mP7Nea2KdBYVH8X1pM0V+
+      action: member
+      state: absent
diff --git a/playbooks/service/service-member-certificate-present.yml b/playbooks/service/service-member-certificate-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bfa01d055d1e5efd8b88422a7b9000469782cc7a
--- /dev/null
+++ b/playbooks/service/service-member-certificate-present.yml
@@ -0,0 +1,15 @@
+---
+- name: Service certificate present.
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  # Ensure service certificate is present
+  - ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/www.example.com
+      certificate:
+        - MIICBjCCAW8CFHnm32VcXaUDGfEGdDL/erPSijUAMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwHhcNMjAwMTIzMDA1NjQ2WhcNMjEwMTIyMDA1NjQ2WjBCMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYrdVmsr7iT3f67DM5bb1osSEe5/c91UUMEIcFq5wrgBhzVfs8iIMDVC1yiUGTsDLJNJc4nb1tUxeR9K5fh25E6n/eWDBP75NStotjAXRU4Ahi3FNRhWFOKesds5xNqgDk5/dY8UekJv2yUblQuZzeF8b2XFrmHuCaYuFctzPfWwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBACF+5RS8Ce0HRixGPu4Xd51i+Kzblg++lx8fDJ8GW5G16/Z1AsB72Hc7etJL2PksHlue/xCq6SA9fIfHc4TBNCiWjPSP1NhHJeYyoPiSkcYsqXuxWyoyRLbnAhBVvhoiqZbUt3u3tGB0uMMA0yJvj07mP7Nea2KdBYVH8X1pM0V+
+      action: member
+      state: present
diff --git a/playbooks/service/service-member-principal-absent.yml b/playbooks/service/service-member-principal-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6bfb168c4f8233384a1c8cdaea9f492c53b10c9c
--- /dev/null
+++ b/playbooks/service/service-member-principal-absent.yml
@@ -0,0 +1,14 @@
+---
+- name: Service member principal absent
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Service HTTP/www.exmaple.com member principals host/test.exmaple.com absent
+    ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/www.example.com
+      principal:
+        - host/test.exmaple.com
+      action: member
+      state: absent
diff --git a/playbooks/service/service-member-principal-present.yml b/playbooks/service/service-member-principal-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..aa94f32e896e568076a2ae017aaac8f224cde09d
--- /dev/null
+++ b/playbooks/service/service-member-principal-present.yml
@@ -0,0 +1,13 @@
+---
+- name: Service member principal present
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Service HTTP/www.exmaple.com member principals host/test.exmaple.com present
+    ipaservice:
+      ipaadmin_password: MyPassword123
+      name: HTTP/www.example.com
+      principal:
+        - host/test.exmaple.com
+      action: member
diff --git a/plugins/modules/ipaservice.py b/plugins/modules/ipaservice.py
new file mode 100644
index 0000000000000000000000000000000000000000..d3074ffe52a418061801acda04330a2795242afe
--- /dev/null
+++ b/plugins/modules/ipaservice.py
@@ -0,0 +1,811 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Rafael Guterres Jeffman <rjeffman@redhat.com>
+#
+# Copyright (C) 2019 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+ANSIBLE_METADATA = {
+    "metadata_version": "1.0",
+    "supported_by": "community",
+    "status": ["preview"],
+}
+
+
+DOCUMENTATION = """
+---
+module: ipaservice
+short description: Manage FreeIPA service
+description: Manage FreeIPA service
+options:
+  ipaadmin_principal:
+    description: The admin principal
+    default: admin
+  ipaadmin_password:
+    description: The admin password
+    required: false
+  name:
+    description: The service to manage
+    required: true
+    aliases: ["service"]
+  certificate:
+    description: Base-64 encoded service certificate.
+    required: false
+    type: list
+    aliases=['usercertificate']
+  pac_type:
+    description: Supported PAC type.
+    required: false
+    choices: ["MS-PAC", "PAD", "NONE"]
+    type: list
+    aliases: ["pac_type", "ipakrbauthzdata"]
+  auth_ind:
+    description: Defines a whitelist for Authentication Indicators.
+    required: false
+    choices: ["otp", "radius", "pkinit", "hardened"]
+    aliases: ["krbprincipalauthind"]
+  skip_host_check:
+    description: Skip checking if host object exists.
+    required: False
+    type: bool
+  force:
+    description: Force principal name even if host is not in DNS.
+    required: False
+    type: bool
+  requires_pre_auth:
+    description: Pre-authentication is required for the service.
+    required: false
+    type: bool
+    default: False
+    aliases: ["ipakrbrequirespreauth"]
+  ok_as_delegate:
+    description: Client credentials may be delegated to the service.
+    required: false
+    type: bool
+    default: False
+    aliases: ["ipakrbokasdelegate"]
+  ok_to_auth_as_delegate: Allow service to authenticate on behalf of a client.
+    description: .
+    required: false
+    type: bool
+    default: False
+    aliases:["ipakrboktoauthasdelegate"]
+  principal:
+    description:
+    required: false
+    type: list
+    aliases: ["krbprincipalname"]
+  host:
+    description: Host that can manage the service.
+    required: false
+    type: list
+    aliases: ["managedby_host"]
+  allow_create_keytab_user:
+    descrption: Users allowed to create a keytab of this host.
+    required: false
+    type: list
+    aliases: ["ipaallowedtoperform_write_keys_user"]
+  allow_create_keytab_group:
+    descrption: Groups allowed to create a keytab of this host.
+    required: false
+    type: list
+    aliases: ["ipaallowedtoperform_write_keys_group"]
+  allow_create_keytab_host:
+    descrption: Hosts allowed to create a keytab of this host.
+    required: false
+    type: list
+    aliases: ["ipaallowedtoperform_write_keys_host"]
+  allow_create_keytab_hostgroup:
+    descrption: Host group allowed to create a keytab of this host.
+    required: false
+    type: list
+    aliases: ["ipaallowedtoperform_write_keys_hostgroup"]
+  allow_retrieve_keytab_user:
+    descrption: User allowed to retrieve a keytab of this host.
+    required: false
+    type: list
+    aliases: ["ipaallowedtoperform_read_keys_user"]
+  allow_retrieve_keytab_group:
+    descrption: Groups allowed to retrieve a keytab of this host.
+    required: false
+    type: list
+    aliases: ["ipaallowedtoperform_read_keys_group"]
+  allow_retrieve_keytab_host:
+    descrption: Hosts allowed to retrieve a keytab of this host.
+    required: false
+    type: list
+    aliases: ["ipaallowedtoperform_read_keys_host"]
+  allow_retrieve_keytab_hostgroup:
+    descrption: Host groups allowed to retrieve a keytab of this host.
+    required: false
+    type: list
+    aliases: ["ipaallowedtoperform_read_keys_hostgroup"]
+  action:
+    description: Work on service or member level
+    default: service
+    choices: ["member", "service"]
+  state:
+    description: State to ensure
+    default: present
+    choices: ["present", "absent", "enabled", "disabled"]
+author:
+    - Rafael Jeffman
+"""
+
+EXAMPLES = """
+  # Ensure service is present
+  - ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/www.example.com
+      pac_type:
+        - MS-PAC
+        - PAD
+      auth_ind: otp
+      skip_host_check: true
+      force: false
+      requires_pre_auth: true
+      ok_as_delegate: false
+      ok_to_auth_as_delegate: false
+
+  # Ensure service is absent
+  - ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/www.example.com
+      state: absent
+
+  # Ensure service member certificate is present.
+  - ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/www.example.com
+      certificate:
+        - MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQELBQAw
+        DzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQxMDhaMA8xDT
+        ALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+XVVGFYpH
+        VkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJzM
+        LJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmjfMXp0nIT
+        oTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn0vQINt55YoEd1s
+        4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpc
+        xj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1
+        UdDgQWBBRV0j7JYukuH/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+Q
+        eNlRLXDlEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs
+        5y1Zp0WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqic
+        uPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkGwIt1OH
+        2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyEiaio3693l6no
+        obyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj9uKQ5GTWRh59VnRBVC
+        /SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV7MVq
+      action: member
+      state: present
+
+  # Ensure principal host/test.example.com present in service.
+  - ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/www.example.com
+      principal:
+        - host/test.example.com
+      action: member
+
+  # Ensure host can manage service.
+  - ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/www.example.com
+      host:
+      - host1.example.com
+      - host2.example.com
+      action: member
+"""
+
+RETURN = """
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
+    temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
+    encode_certificate, gen_add_del_lists, module_params_get, to_text, \
+    api_check_param
+
+
+def find_service(module, name):
+    _args = {
+        "all": True,
+    }
+
+    _result = api_command(module, "service_find", to_text(name), _args)
+
+    if len(_result["result"]) > 1:
+        module.fail_json(
+            msg="There is more than one service '%s'" % (name))
+    elif len(_result["result"]) == 1:
+        _res = _result["result"][0]
+        certs = _res.get("usercertificate")
+        if certs is not None:
+            _res["usercertificate"] = [encode_certificate(cert) for
+                                       cert in certs]
+        return _res
+    else:
+        return None
+
+
+def gen_args(pac_type, auth_ind, skip_host_check, force, requires_pre_auth,
+             ok_as_delegate, ok_to_auth_as_delegate):
+    _args = {}
+
+    if pac_type is not None:
+        _args['ipakrbauthzdata'] = pac_type
+    if auth_ind is not None:
+        _args['krbprincipalauthind'] = auth_ind
+    if skip_host_check is not None:
+        _args['skip_host_check'] = (skip_host_check)
+    if force is not None:
+        _args['force'] = (force)
+    if requires_pre_auth is not None:
+        _args['ipakrbrequirespreauth'] = (requires_pre_auth)
+    if ok_as_delegate is not None:
+        _args['ipakrbokasdelegate'] = (ok_as_delegate)
+    if ok_to_auth_as_delegate is not None:
+        _args['ipakrboktoauthasdelegate'] = (ok_to_auth_as_delegate)
+
+    return _args
+
+
+def check_parameters(module, state, action, names, parameters):
+    assert isinstance(parameters, dict)
+
+    # invalid parameters for everything but state 'present', action 'service'.
+    invalid = ['pac_type', 'auth_ind', 'skip_host_check',
+               'force', 'requires_pre_auth', 'ok_as_delegate',
+               'ok_to_auth_as_delegate']
+
+    # invalid parameters when not handling service members.
+    invalid_not_member = \
+        ['principal', 'certificate', 'host', 'allow_create_keytab_user',
+         'allow_create_keytab_group', 'allow_create_keytab_host',
+         'allow_create_keytab_hostgroup', 'allow_retrieve_keytab_user',
+         'allow_retrieve_keytab_group', 'allow_retrieve_keytab_host',
+         'allow_retrieve_keytab_hostgroup']
+
+    if state == 'present':
+        if len(names) != 1:
+            module.fail_json(msg="Only one service can be added at a time.")
+
+        if action == 'service':
+            invalid = []
+
+    elif state == 'absent':
+        if len(names) < 1:
+            module.fail_json(msg="No name given.")
+
+        if action == "service":
+            invalid.extend(invalid_not_member)
+
+    elif state == 'disabled':
+        invalid.extend(invalid_not_member)
+        if action != "service":
+            module.fail_json(
+                msg="Invalid action '%s' for state '%s'" % (action, state))
+
+    else:
+        module.fail_json(msg="Invalid state '%s'" % (state))
+
+    for _invalid in invalid:
+        if parameters[_invalid] is not None:
+            module.fail_json(
+                msg="Argument '%s' can not be used with state '%s'" %
+                (_invalid, state))
+
+
+def init_ansible_module():
+    ansible_module = AnsibleModule(
+        argument_spec=dict(
+            # general
+            ipaadmin_principal=dict(type="str", default="admin"),
+            ipaadmin_password=dict(type="str", required=False, no_log=True),
+
+            name=dict(type="list", aliases=["service"], default=None,
+                      required=True),
+            # service attributesstr
+            certificate=dict(type="list", aliases=['usercertificate'],
+                             default=None, required=False),
+            principal=dict(type="list", aliases=["krbprincipalname"],
+                           default=None),
+            pac_type=dict(type="list", aliases=["ipakrbauthzdata"],
+                          choices=["MS-PAC", "PAD", "NONE"]),
+            auth_ind=dict(type="str",
+                          aliases=["krbprincipalauthind"],
+                          choices=["otp", "radius", "pkinit", "hardened"]),
+            skip_host_check=dict(type="bool"),
+            force=dict(type="bool"),
+            requires_pre_auth=dict(
+                type="bool", aliases=["ipakrbrequirespreauth"]),
+            ok_as_delegate=dict(type="bool", aliases=["ipakrbokasdelegate"]),
+            ok_to_auth_as_delegate=dict(type="bool",
+                                        aliases=["ipakrboktoauthasdelegate"]),
+            host=dict(type="list", aliases=["managedby_host"], required=False),
+            allow_create_keytab_user=dict(
+                type="list", required=False,
+                aliases=['ipaallowedtoperform_write_keys_user']),
+            allow_retrieve_keytab_user=dict(
+                type="list", required=False,
+                aliases=['ipaallowedtoperform_read_keys_user']),
+            allow_create_keytab_group=dict(
+                type="list", required=False,
+                aliases=['ipaallowedtoperform_write_keys_group']),
+            allow_retrieve_keytab_group=dict(
+                type="list", required=False,
+                aliases=['ipaallowedtoperform_read_keys_group']),
+            allow_create_keytab_host=dict(
+                type="list", required=False,
+                aliases=['ipaallowedtoperform_write_keys_host']),
+            allow_retrieve_keytab_host=dict(
+                type="list", required=False,
+                aliases=['ipaallowedtoperform_read_keys_host']),
+            allow_create_keytab_hostgroup=dict(
+                type="list", required=False,
+                aliases=['ipaallowedtoperform_write_keys_hostgroup']),
+            allow_retrieve_keytab_hostgroup=dict(
+                type="list", required=False,
+                aliases=['ipaallowedtoperform_read_keys_hostgroup']),
+            # action
+            action=dict(type="str", default="service",
+                        choices=["member", "service"]),
+            # state
+            state=dict(type="str", default="present",
+                       choices=["present", "absent",
+                                "enabled", "disabled"]),
+        ),
+        supports_check_mode=True,
+    )
+
+    ansible_module._ansible_debug = True
+
+    return ansible_module
+
+
+def main():
+    ansible_module = init_ansible_module()
+
+    # Get parameters
+
+    # general
+    ipaadmin_principal = module_params_get(ansible_module,
+                                           "ipaadmin_principal")
+    ipaadmin_password = module_params_get(ansible_module, "ipaadmin_password")
+    names = module_params_get(ansible_module, "name")
+
+    # service attributes
+    principal = module_params_get(ansible_module, "principal")
+    certificate = module_params_get(ansible_module, "certificate")
+    pac_type = module_params_get(ansible_module, "pac_type")
+    auth_ind = module_params_get(ansible_module, "auth_ind")
+    skip_host_check = module_params_get(ansible_module, "skip_host_check")
+    force = module_params_get(ansible_module, "force")
+    requires_pre_auth = module_params_get(ansible_module, "requires_pre_auth")
+    ok_as_delegate = module_params_get(ansible_module, "ok_as_delegate")
+    ok_to_auth_as_delegate = module_params_get(ansible_module,
+                                               "ok_to_auth_as_delegate")
+
+    host = module_params_get(ansible_module, "host")
+
+    allow_create_keytab_user = module_params_get(
+        ansible_module, "allow_create_keytab_user")
+    allow_create_keytab_group = module_params_get(
+        ansible_module, "allow_create_keytab_group")
+    allow_create_keytab_host = module_params_get(
+        ansible_module, "allow_create_keytab_host")
+    allow_create_keytab_hostgroup = module_params_get(
+        ansible_module, "allow_create_keytab_hostgroup")
+
+    allow_retrieve_keytab_user = module_params_get(
+        ansible_module, "allow_retrieve_keytab_user")
+    allow_retrieve_keytab_group = module_params_get(
+        ansible_module, "allow_retrieve_keytab_group")
+    allow_retrieve_keytab_host = module_params_get(
+        ansible_module, "allow_create_keytab_host")
+    allow_retrieve_keytab_hostgroup = module_params_get(
+        ansible_module, "allow_retrieve_keytab_hostgroup")
+
+    # action
+    action = module_params_get(ansible_module, "action")
+    # state
+    state = module_params_get(ansible_module, "state")
+
+    # check parameters
+    check_parameters(ansible_module, state, action, names, vars())
+
+    # Init
+
+    changed = False
+    exit_args = {}
+    ccache_dir = None
+    ccache_name = None
+    try:
+        if not valid_creds(ansible_module, ipaadmin_principal):
+            ccache_dir, ccache_name = temp_kinit(ipaadmin_principal,
+                                                 ipaadmin_password)
+        api_connect()
+
+        has_skip_host_check = api_check_param(
+            "service_add", "skip_host_check")
+        if skip_host_check and not has_skip_host_check:
+            ansible_module.fail_json(
+                msg="Skipping host check is not supported by your IPA version")
+
+        commands = []
+
+        for name in names:
+            res_find = find_service(ansible_module, name)
+
+            if state == "present":
+                if action == "service":
+                    args = gen_args(
+                        pac_type, auth_ind, skip_host_check, force,
+                        requires_pre_auth, ok_as_delegate,
+                        ok_to_auth_as_delegate)
+                    if not has_skip_host_check and 'skip_host_check' in args:
+                        del args['skip_host_check']
+
+                    if res_find is None:
+                        commands.append([name, 'service_add', args])
+
+                        certificate_add = certificate or []
+                        certificate_del = []
+                        host_add = host or []
+                        host_del = []
+                        principal_add = principal or []
+                        principal_del = []
+                        allow_create_keytab_user_add = \
+                            allow_create_keytab_user or []
+                        allow_create_keytab_user_del = []
+                        allow_create_keytab_group_add = \
+                            allow_create_keytab_group or []
+                        allow_create_keytab_group_del = []
+                        allow_create_keytab_host_add = \
+                            allow_create_keytab_host or []
+                        allow_create_keytab_host_del = []
+                        allow_create_keytab_hostgroup_add = \
+                            allow_create_keytab_hostgroup or []
+                        allow_create_keytab_hostgroup_del = []
+                        allow_retrieve_keytab_user_add = \
+                            allow_retrieve_keytab_user or []
+                        allow_retrieve_keytab_user_del = []
+                        allow_retrieve_keytab_group_add = \
+                            allow_retrieve_keytab_group or []
+                        allow_retrieve_keytab_group_del = []
+                        allow_retrieve_keytab_host_add = \
+                            allow_retrieve_keytab_host or []
+                        allow_retrieve_keytab_host_del = []
+                        allow_retrieve_keytab_hostgroup_add = \
+                            allow_retrieve_keytab_hostgroup or []
+                        allow_retrieve_keytab_hostgroup_del = []
+
+                    else:
+                        for remove in ['skip_host_check', 'force']:
+                            if remove in args:
+                                del args[remove]
+
+                        if not compare_args_ipa(ansible_module, args,
+                                                res_find):
+                            commands.append([name, "service_mod", args])
+
+                        certificate_add, certificate_del = gen_add_del_lists(
+                            certificate, res_find.get("usercertificate"))
+
+                        host_add, host_del = gen_add_del_lists(
+                            host, res_find.get('managedby_host', []))
+
+                        principal_add, principal_del = gen_add_del_lists(
+                            principal, res_find.get("principal"))
+
+                        (allow_create_keytab_user_add,
+                         allow_create_keytab_user_del) = \
+                            gen_add_del_lists(
+                                allow_create_keytab_user, res_find.get(
+                                    'ipaallowedtoperform_write_keys_user',
+                                    []))
+                        (allow_retrieve_keytab_user_add,
+                         allow_retrieve_keytab_user_del) = \
+                            gen_add_del_lists(
+                                allow_retrieve_keytab_user, res_find.get(
+                                    'ipaallowedtoperform_read_keys_user',
+                                    []))
+                        (allow_create_keytab_group_add,
+                         allow_create_keytab_group_del) = \
+                            gen_add_del_lists(
+                                allow_create_keytab_group, res_find.get(
+                                    'ipaallowedtoperform_write_keys_group',
+                                    []))
+                        (allow_retrieve_keytab_group_add,
+                         allow_retrieve_keytab_group_del) = \
+                            gen_add_del_lists(
+                                allow_retrieve_keytab_group,
+                                res_find.get(
+                                    'ipaallowedtoperform_read_keys_group',
+                                    []))
+                        (allow_create_keytab_host_add,
+                         allow_create_keytab_host_del) = \
+                            gen_add_del_lists(
+                                allow_create_keytab_host,
+                                res_find.get(
+                                    'ipaallowedtoperform_write_keys_host',
+                                    []))
+                        (allow_retrieve_keytab_host_add,
+                         allow_retrieve_keytab_host_del) = \
+                            gen_add_del_lists(
+                                allow_retrieve_keytab_host,
+                                res_find.get(
+                                    'ipaallowedtoperform_read_keys_host',
+                                    []))
+                        (allow_create_keytab_hostgroup_add,
+                         allow_create_keytab_hostgroup_del) = \
+                            gen_add_del_lists(
+                                allow_create_keytab_hostgroup,
+                                res_find.get(
+                                    'ipaallowedtoperform_write_keys_hostgroup',
+                                    []))
+                        (allow_retrieve_keytab_hostgroup_add,
+                         allow_retrieve_keytab_hostgroup_del) = \
+                            gen_add_del_lists(
+                                allow_retrieve_keytab_hostgroup,
+                                res_find.get(
+                                    'ipaallowedtoperform_read_keys_hostgroup',
+                                    []))
+
+                elif action == "member":
+                    if res_find is None:
+                        ansible_module.fail_json(msg="No service '%s'" % name)
+
+                    existing = res_find.get('usercertificate', [])
+                    if certificate is None:
+                        certificate_add = []
+                    else:
+                        certificate_add = [c for c in certificate
+                                           if c not in existing]
+                    certificate_del = []
+                    host_add = host or []
+                    host_del = []
+                    principal_add = principal or []
+                    principal_del = []
+
+                    allow_create_keytab_user_add = \
+                        allow_create_keytab_user or []
+                    allow_create_keytab_user_del = []
+                    allow_create_keytab_group_add = \
+                        allow_create_keytab_group or []
+                    allow_create_keytab_group_del = []
+                    allow_create_keytab_host_add = \
+                        allow_create_keytab_host or []
+                    allow_create_keytab_host_del = []
+                    allow_create_keytab_hostgroup_add = \
+                        allow_create_keytab_hostgroup or []
+                    allow_create_keytab_hostgroup_del = []
+                    allow_retrieve_keytab_user_add = \
+                        allow_retrieve_keytab_user or []
+                    allow_retrieve_keytab_user_del = []
+                    allow_retrieve_keytab_group_add = \
+                        allow_retrieve_keytab_group or []
+                    allow_retrieve_keytab_group_del = []
+                    allow_retrieve_keytab_host_add = \
+                        allow_retrieve_keytab_host or []
+                    allow_retrieve_keytab_host_del = []
+                    allow_retrieve_keytab_hostgroup_add = \
+                        allow_retrieve_keytab_hostgroup or []
+                    allow_retrieve_keytab_hostgroup_del = []
+
+                # Add principals
+                for _principal in principal_add:
+                    commands.append([name, "service_add_principal",
+                                     {
+                                         "krbprincipalname":
+                                         _principal,
+                                     }])
+
+                # Remove principals
+                for _principal in principal_del:
+                    commands.append([name, "service_remove_principal",
+                                     {
+                                         "krbprincipalname":
+                                         _principal,
+                                     }])
+
+                for _certificate in certificate_add:
+                    commands.append([name, "service_add_cert",
+                                     {
+                                         "usercertificate":
+                                         _certificate,
+                                     }])
+                # Remove certificates
+                for _certificate in certificate_del:
+                    commands.append([name, "service_remove_cert",
+                                     {
+                                         "usercertificate":
+                                         _certificate,
+                                     }])
+
+                # Add hosts.
+                if host is not None and len(host) > 0 and len(host_add) > 0:
+                    commands.append([name, "service_add_host",
+                                     {"host": host_add}])
+                # Remove hosts
+                if host is not None and len(host) > 0 and len(host_del) > 0:
+                    commands.append([name, "service_remove_host",
+                                     {"host": host_del}])
+
+                # Allow create keytab
+                if len(allow_create_keytab_user_add) > 0 or \
+                   len(allow_create_keytab_group_add) > 0 or \
+                   len(allow_create_keytab_host_add) > 0 or \
+                   len(allow_create_keytab_hostgroup_add) > 0:
+                    commands.append(
+                        [name, "service_allow_create_keytab",
+                         {'user': allow_create_keytab_user_add,
+                          'group': allow_create_keytab_group_add,
+                          'host': allow_create_keytab_host_add,
+                          'hostgroup': allow_create_keytab_hostgroup_add
+                          }])
+
+                # Disallow create keytab
+                if len(allow_create_keytab_user_del) > 0 or \
+                   len(allow_create_keytab_group_del) > 0 or \
+                   len(allow_create_keytab_host_del) > 0 or \
+                   len(allow_create_keytab_hostgroup_del) > 0:
+                    commands.append(
+                        [name, "service_disallow_create_keytab",
+                         {'user': allow_create_keytab_user_del,
+                          'group': allow_create_keytab_group_del,
+                          'host': allow_create_keytab_host_del,
+                          'hostgroup': allow_create_keytab_hostgroup_del
+                          }])
+
+                # Allow retrieve keytab
+                if len(allow_retrieve_keytab_user_add) > 0 or \
+                   len(allow_retrieve_keytab_group_add) > 0 or \
+                   len(allow_retrieve_keytab_hostgroup_add) > 0 or \
+                   len(allow_retrieve_keytab_hostgroup_add) > 0:
+                    commands.append(
+                        [name, "service_allow_retrieve_keytab",
+                         {'user': allow_retrieve_keytab_user_add,
+                          'group': allow_retrieve_keytab_group_add,
+                          'host': allow_retrieve_keytab_host_add,
+                          'hostgroup': allow_retrieve_keytab_hostgroup_add
+                          }])
+
+                # Disllow retrieve keytab
+                if len(allow_retrieve_keytab_user_del) > 0 or \
+                   len(allow_retrieve_keytab_group_del) > 0 or \
+                   len(allow_retrieve_keytab_host_del) > 0 or \
+                   len(allow_retrieve_keytab_hostgroup_del) > 0:
+                    commands.append(
+                        [name, "service_disallow_retrieve_keytab",
+                         {'user': allow_retrieve_keytab_user_del,
+                          'group': allow_retrieve_keytab_group_del,
+                          'host': allow_retrieve_keytab_host_del,
+                          'hostgroup': allow_retrieve_keytab_hostgroup_del
+                          }])
+
+            elif state == "absent":
+                if action == "service":
+                    if res_find is not None:
+                        commands.append([name, 'service_del', {}])
+
+                elif action == "member":
+                    if res_find is None:
+                        ansible_module.fail_json(msg="No service '%s'" % name)
+
+                    # Remove principals
+                    if principal is not None:
+                        for _principal in principal:
+                            commands.append([name, "service_remove_principal",
+                                             {
+                                                 "krbprincipalname":
+                                                 _principal,
+                                             }])
+                    # Remove certificates
+                    if certificate is not None:
+                        existing = res_find.get('usercertificate', [])
+                        for _certificate in certificate:
+                            if _certificate in existing:
+                                commands.append([name, "service_remove_cert",
+                                                 {
+                                                     "usercertificate":
+                                                     _certificate,
+                                                 }])
+
+                    # Add hosts
+                    if host is not None:
+                        commands.append(
+                            [name, "service_remove_host", {"host": host}])
+
+                    # Allow create keytab
+                    if allow_create_keytab_user is not None or \
+                       allow_create_keytab_group is not None or \
+                       allow_create_keytab_host is not None or \
+                       allow_create_keytab_hostgroup is not None:
+                        commands.append(
+                            [name, "service_disallow_create_keytab",
+                             {'user': allow_create_keytab_user,
+                              'group': allow_create_keytab_group,
+                              'host': allow_create_keytab_host,
+                              'hostgroup': allow_create_keytab_hostgroup
+                              }])
+
+                    # Allow retriev keytab
+                    if allow_retrieve_keytab_user is not None or \
+                       allow_retrieve_keytab_group is not None or \
+                       allow_retrieve_keytab_host is not None or \
+                       allow_retrieve_keytab_hostgroup is not None:
+                        commands.append(
+                            [name, "service_disallow_retrieve_keytab",
+                             {'user': allow_retrieve_keytab_user,
+                              'group': allow_retrieve_keytab_group,
+                              'host': allow_retrieve_keytab_host,
+                              'hostgroup': allow_retrieve_keytab_hostgroup
+                              }])
+
+            elif state == "disabled":
+                if action == "service":
+                    if res_find is not None and \
+                       len(res_find.get('usercertificate', [])) > 0:
+                        commands.append([name, 'service_disable', {}])
+                else:
+                    ansible_module.fail_json(
+                        msg="Invalid action '%s' for state '%s'" %
+                        (action, state))
+            else:
+                ansible_module.fail_json(msg="Unkown state '%s'" % state)
+
+        # Execute commands
+        errors = []
+        for name, command, args in commands:
+            try:
+                result = api_command(ansible_module, command, name, args)
+
+                if "completed" in result:
+                    if result["completed"] > 0:
+                        changed = True
+                else:
+                    changed = True
+            except Exception as ex:
+                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
+                                                             str(ex)))
+            # Get all errors
+            # All "already a member" and "not a member" failures in the
+            # result are ignored. All others are reported.
+            if "failed" in result and len(result["failed"]) > 0:
+                for item in result["failed"]:
+                    failed_item = result["failed"][item]
+                    for member_type in failed_item:
+                        for member, failure in failed_item[member_type]:
+                            if "already a member" in failure \
+                               or "not a member" in failure:
+                                continue
+                            errors.append("%s: %s %s: %s" % (
+                                command, member_type, member, failure))
+        if len(errors) > 0:
+            ansible_module.fail_json(msg=", ".join(errors))
+
+    except Exception as ex:
+        ansible_module.fail_json(msg=str(ex))
+
+    finally:
+        temp_kdestroy(ccache_dir, ccache_name)
+
+    # Done
+    ansible_module.exit_json(changed=changed, **exit_args)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/service/certificate/cert1.der b/tests/service/certificate/cert1.der
new file mode 100644
index 0000000000000000000000000000000000000000..b1b90efde6d33ab44ca7b62941eee06fdbc05da9
Binary files /dev/null and b/tests/service/certificate/cert1.der differ
diff --git a/tests/service/certificate/cert1.pem b/tests/service/certificate/cert1.pem
new file mode 100644
index 0000000000000000000000000000000000000000..ab3704bba5957ba125762cfb3b5b11b1067b20a8
--- /dev/null
+++ b/tests/service/certificate/cert1.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQx
+MDhaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC+XVVGFYpHVkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk
++OfEkzLUAhWBOwEraELJzMLJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa
+8KvgRVRyGLoVvGbLJvmjfMXp0nIToTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0am
+nvsgparYXhypHaRLnikn0vQINt55YoEd1s4KrvEcD2VdZkIMPbLRu2zFvMprF3cj
+QQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpcxj9aGUVt5bsq1D+Tzj3GsidS
+X0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1UdDgQWBBRV0j7JYuku
+H/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+QeNlRLXDlEDAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs5y1Zp0
+WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqic
+uPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkG
+wIt1OH2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyE
+iaio3693l6noobyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj
+9uKQ5GTWRh59VnRBVC/SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV
+7MVq
+-----END CERTIFICATE-----
diff --git a/tests/service/certificate/cert2.der b/tests/service/certificate/cert2.der
new file mode 100644
index 0000000000000000000000000000000000000000..e176c2ba50270b331c457e784dea5f5f6a09e53a
Binary files /dev/null and b/tests/service/certificate/cert2.der differ
diff --git a/tests/service/certificate/cert2.pem b/tests/service/certificate/cert2.pem
new file mode 100644
index 0000000000000000000000000000000000000000..e8ea2e434b79dd142b7531c03ab105ddd2d5d5fd
--- /dev/null
+++ b/tests/service/certificate/cert2.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIURhps6LEteMDCdBrlVkWe4cgSh0YwDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQyNDBaFw0zMDAyMDExNDQy
+NDBaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC4W56H0VraEKGlCxSTS2PqnaD11shMjruexmholmTEtYPePPnQHpwiiZlg
+K7CPBIOdCn4hHH+hXQDg/TJRMjrde1VzD0pFRBUq6H25sy8oOlfD0bDXkncWn82S
+OJu2UJHeL7htQLRxW14VIAO2YO9zaXdophy6/csTAkFq1ls/vTBp73pnnYp8D7Tg
+zBB6bb95OZBSHeCzPIH2FSCJ/W0j6bHw4i7uHu/jWx0o0LR152fSFFwk0Wrmp8HH
+b2083OlnSBgTM+BZDg9rB7jpLCsIGHWXbjG36jmRaZu5z4vq2FNomJ8PXkX7mwUf
+aft6z+px7UlhrwUxEVWIXOoUBYcJAgMBAAGjUzBRMB0GA1UdDgQWBBTttCQn5UaQ
+i+N5WRnA7ZTQlkVfRTAfBgNVHSMEGDAWgBTttCQn5UaQi+N5WRnA7ZTQlkVfRTAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBW3vRR5wEDztuLVrcQ
+Dojn1XB24OOqn4C6OJyz3FUxd4MQA8J2vKN4P2QXhY0oYsauFKhR5xfOaDUcK2Tu
+kAtFz1mxqm1ygUVQHbrs8lBeIi4hoMc76ODJ/V9GNY7N/y/5xtD7XlyTVT2tb6tc
+6tmv8e4497PTPspuHp9YbbvzdSI12JENDW4hKCOpR/Uv7mRcCT+c2iMJdUL3f3YO
+FsGBbxVdTPmuhL4My8qR/CtCNpN0gBsaxUKFAP+/1AvFbFDChFVDEEdD8PLznH5x
+8HLmA9/K5x/cXbgqESUqK13P53f1XYOfggKb1f7yqBAZRnTY82+k9Kn9qWOcnyxS
+uUtZ
+-----END CERTIFICATE-----
diff --git a/tests/service/certificate/private1.key b/tests/service/certificate/private1.key
new file mode 100644
index 0000000000000000000000000000000000000000..372908d226ff4d711846f4a4097d8059437b0388
--- /dev/null
+++ b/tests/service/certificate/private1.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+XVVGFYpHVkcD
+fVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJ
+zMLJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmj
+fMXp0nIToTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn
+0vQINt55YoEd1s4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQW
+AnAsu7OJEkNF+E4Mkmpcxj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63
+g5cZyE+nAgMBAAECggEBALJIsw5aKhE5inSIN0xZT3FTWxcjHF26jE+X86G0H3KZ
+roLqnjOagOKTwjeErXt66IWKFh3b5vKCSNq6PEs8OCeRHv71bay5zK1WWLH87sKJ
+EAUSPuK5O6donI9aC36VL8tTwSOOOS9WJ0KoHqsn/tLHlONXOvo063iYEg8xFhuP
+etrOf2gDjwGbeWis7VeHG7wL5p2/WdsyjTDbQPhmUlBO93rtkBlm9FaqYKwrp8qe
+4c5gf6ZAKgY2EZaQuEvq3Lonk7TRCtPDVCPLYQxZGOmn2UeUS+HMnDSqrlQesBKD
+hNNCCJVaQZHsghmwXa8t9yRBIxoOqVObdEQYJ8wuxMECgYEA676x3m7T2PwJXS+q
+Km3snv60lCozxKbzaNJ1xlAmpW08MijYCkDS/kWSIwN5GO+b5B6use8iALrV9SyP
+eC/6bFuMJ+zRfGhn1cw4Ibz79EroTxmJio7J7SiD/yxvjNVznKx5xgQeB9tdgjaf
+yHSxInWoQzcDGKUe2h2KFJxUzJECgYEAzrh6zI8Ugne5iBUbLcpJUehlMd4+RM0l
+1y8ZOBS1tjzimWycjZaPtMB0q4FOc1ou2zcSxwoGIv5khvUsjKhTfOc6lK+cHPhE
+fAppYUxhHw2UDpX/0hKDuDu++O+86ANp7AOvM+KcNAiEoovxUyurVjBsT/PPlrTA
+r5w7xuyi1LcCgYAZ7ZdSh431R4MgJKXqlLx5oDnsMdgPwOz0knExpo8ZkrIUMjnQ
+puCN5sjz4OXowDG9HULJfyuWOPZfSM9ewKgiUs9PdNR1gmYpNZTW4Ro0/CggywY9
+nwbGdrZN0m1SaAeXK8EY7kr/Qjk+oRNh0LPKvnYLLnnAtCh4hNcy/R62gQKBgBaD
+3UweYVt8csaxlc489BNpvmvaCuovdemkBZkoGEqLAxs2yy5Ysbo8I/jyEntZ3TSf
+IPpwyw5Qqt5QIdQIGV/HR4geQGCfYcYo1CV2zjU1o2SbTcuxnIsaZshyRB75EDZW
+iGScT+sS6m9R0qz+WqD+kS18HqYJddsqpxAZgfqtAoGBAJx7E8HxFpaNfz/QQPAQ
+mvON6ub5u4AfhH4DgiPErMxNsdzVICL+mnQy0wdmi1oEpq9KH4/8aSxdPhadyl/8
+l+0CkCkBZvEP7+NmctR8Zot60wS0DnOwuURCxm/zYJ26DXjB0XitDDumFJ56Wd6p
+uLl9eKMBE/jBsCSWQTuwrtnT
+-----END PRIVATE KEY-----
diff --git a/tests/service/certificate/private2.key b/tests/service/certificate/private2.key
new file mode 100644
index 0000000000000000000000000000000000000000..58909dbf5990ff703c7d70395edb0899ce90fb3d
--- /dev/null
+++ b/tests/service/certificate/private2.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC4W56H0VraEKGl
+CxSTS2PqnaD11shMjruexmholmTEtYPePPnQHpwiiZlgK7CPBIOdCn4hHH+hXQDg
+/TJRMjrde1VzD0pFRBUq6H25sy8oOlfD0bDXkncWn82SOJu2UJHeL7htQLRxW14V
+IAO2YO9zaXdophy6/csTAkFq1ls/vTBp73pnnYp8D7TgzBB6bb95OZBSHeCzPIH2
+FSCJ/W0j6bHw4i7uHu/jWx0o0LR152fSFFwk0Wrmp8HHb2083OlnSBgTM+BZDg9r
+B7jpLCsIGHWXbjG36jmRaZu5z4vq2FNomJ8PXkX7mwUfaft6z+px7UlhrwUxEVWI
+XOoUBYcJAgMBAAECggEAPTBrlbiu5uHORPFAiwLizuQyoGYBZSearkA8Nzpzh7aX
+ZhPm9mSyfeQdvAXEPDPLWzw4UNUcp3ou6H4hTUHWt9xPqDjS9dp7DBrOX+xRIpD6
+wEvA3kwGqsOvf3C6ffCP+abtF5X6TgV9XJWbpdTWpP/EWj+IGahS1qRRAhzTfHvF
+YGMTFwlgbz4eOs+FXBnVNGsdsdMLpOyqHMdDAA4BhyspWHyHgCRjEjROuJCKSDUR
+MD1pNdaEYzoj5QeE1IKzXAzTaxG/YKd36BxV5Cp9DOBuZZLgNEd2EisXxV7UwZL4
+leGgxAc+KQs6QoPoz+mrKbdDnxe6V+uaa9KHoqnj4QKBgQD1qh+MEIF+Vuf/keFJ
+vDgS7oFeg1UGzMtWypiNfVYu9cBLp32tgY48+ey3OCvhRSJAVROH1rc5ZfkESSQ2
+rSeV/T3plr5bBkLc7chuDM8An745p8VSOM+Ak1zE2qb+Qo+IsxNRA9KyeUvupuB8
+HJ7fxdZ7JpgueD/mKyCn1WaGIwKBgQDAHTS6J7LKm52d2norERK6ZyBNVhKaKNDW
+ssRqSh906oFU63Qijsp4dbm1iRXGME6Zoe1quN/K80iATdv/VzjzxS1Of8mqA7gr
+/2juZbpEluSxjkqPAZp1p4Kx9WURdzv2ModkYwM3zSTGR5l22Whd9QdNQvVl1mf0
++RfgE6ty4wKBgA+GtwO1L1n6yCLg52ovmSOpK0f76O3LF7beixG2MDI7mfGuHkVP
+ANxdt1ZFGJDeO7HxLpDRQzc/eKOKs904yF20aatPuawrEyK/bIF4EcUqU211awUt
+TgAEUEKoxxEex8+N8dSW90QMYn4s0ddGP8xIxqt13vxg4Tj81M2GsTodAoGAOa8L
+S/Hrj0ZWdzVIhXHk669XVaFIiJ1Ex5J5w2hqNZLMLpFcF5xEUxMWJdn5fb63ew3R
+2b+VAr01wcCfE/Y+lYNY7T8VcEUZoaxY92v4F+wu0tlkrbfPhxA6//As3qesi2n0
+mUHZj4G7TwXkoHj7C2stPBek02UjZbz9XDzLt/0CgYAiawpqmHJK4LhRm+P6J1+X
+nzLPzQ6t15ivh4jPrWZPgOG3hKV+If+PTv7lLy51y2X4Ttuyumy09J+kYiy3qIHR
+nmbAEkc9lesrxk1eytxmYY+fGTBpaLAc+vNXWCtUc1ttKcfrPhZdncmSh5Z0aFN3
+D+EddEZHzfzoGlfbNVkfmQ==
+-----END PRIVATE KEY-----
diff --git a/tests/service/certificate/test_service_certificate.yml b/tests/service/certificate/test_service_certificate.yml
new file mode 100644
index 0000000000000000000000000000000000000000..89c46f10e69aecf419e354cd68bc995c54604d4e
--- /dev/null
+++ b/tests/service/certificate/test_service_certificate.yml
@@ -0,0 +1,225 @@
+#
+# Generate self-signed certificates using openssl:
+#
+#   openssl req -x509 -newkey rsa:2048 -days 3650 -nodes -keyout private1.key -out cert1.pem -subj '/CN=test'
+#   openssl req -x509 -newkey rsa:2048 -days 3650 -nodes -keyout private2.key -out cert2.pem -subj '/CN=test'
+#
+# Convert the certificate do DER for easier handling through CLI
+#
+#   openssl x509 -outform der -in cert1.pem -out cert1.der
+#   openssl x509 -outform der -in cert2.pem -out cert2.der
+#
+# Use base64:
+#
+#  base64 cert1.der -w5000
+#  base64 cert2.der -w5000
+#
+# Certificates:
+#    cert1:
+#    - MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQxMDhaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+XVVGFYpHVkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJzMLJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmjfMXp0nIToTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn0vQINt55YoEd1s4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpcxj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1UdDgQWBBRV0j7JYukuH/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+QeNlRLXDlEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs5y1Zp0WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqicuPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkGwIt1OH2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyEiaio3693l6noobyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj9uKQ5GTWRh59VnRBVC/SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV7MVq
+#    cert2:
+#    - MIIC/zCCAeegAwIBAgIURhps6LEteMDCdBrlVkWe4cgSh0YwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQyNDBaFw0zMDAyMDExNDQyNDBaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4W56H0VraEKGlCxSTS2PqnaD11shMjruexmholmTEtYPePPnQHpwiiZlgK7CPBIOdCn4hHH+hXQDg/TJRMjrde1VzD0pFRBUq6H25sy8oOlfD0bDXkncWn82SOJu2UJHeL7htQLRxW14VIAO2YO9zaXdophy6/csTAkFq1ls/vTBp73pnnYp8D7TgzBB6bb95OZBSHeCzPIH2FSCJ/W0j6bHw4i7uHu/jWx0o0LR152fSFFwk0Wrmp8HHb2083OlnSBgTM+BZDg9rB7jpLCsIGHWXbjG36jmRaZu5z4vq2FNomJ8PXkX7mwUfaft6z+px7UlhrwUxEVWIXOoUBYcJAgMBAAGjUzBRMB0GA1UdDgQWBBTttCQn5UaQi+N5WRnA7ZTQlkVfRTAfBgNVHSMEGDAWgBTttCQn5UaQi+N5WRnA7ZTQlkVfRTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBW3vRR5wEDztuLVrcQDojn1XB24OOqn4C6OJyz3FUxd4MQA8J2vKN4P2QXhY0oYsauFKhR5xfOaDUcK2TukAtFz1mxqm1ygUVQHbrs8lBeIi4hoMc76ODJ/V9GNY7N/y/5xtD7XlyTVT2tb6tc6tmv8e4497PTPspuHp9YbbvzdSI12JENDW4hKCOpR/Uv7mRcCT+c2iMJdUL3f3YOFsGBbxVdTPmuhL4My8qR/CtCNpN0gBsaxUKFAP+/1AvFbFDChFVDEEdD8PLznH5x8HLmA9/K5x/cXbgqESUqK13P53f1XYOfggKb1f7yqBAZRnTY82+k9Kn9qWOcnyxSuUtZ
+
+---
+- name: Test service certificates
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  # setup
+  - name: Get Domain from server name
+    set_fact:
+      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
+    when: ipaserver_domain is not defined
+
+  - name: Get IPv4 address prefix from server node
+    set_fact:
+      ipv4_prefix: "{{ ansible_default_ipv4.address.split('.')[:-1] |
+                       join('.') }}"
+
+  - name: Set test host FQDN
+    set_fact:
+      test_subdomain: testcert
+      test_host: "{{ 'testcert.' + ipaserver_domain }}"
+
+  - name: Host test absent
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name: "{{ test_host }}"
+      update_dns: yes
+      state: absent
+
+  - name: Host test present
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name: "{{ test_host }}"
+      ip_address: "{{ ipv4_prefix + '.201' }}"
+      update_dns: yes
+
+  - name: Ensure testing group group01 is present.
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: group01
+
+  - name: Ensure testing group group02 is present.
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: group02
+
+  - name: Ensure services are absent.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ test_host }}"
+      state: absent
+
+  # tests
+  - name: Ensure service is present
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ test_host }}"
+      certificate:
+        - MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQxMDhaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+XVVGFYpHVkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJzMLJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmjfMXp0nIToTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn0vQINt55YoEd1s4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpcxj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1UdDgQWBBRV0j7JYukuH/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+QeNlRLXDlEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs5y1Zp0WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqicuPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkGwIt1OH2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyEiaio3693l6noobyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj9uKQ5GTWRh59VnRBVC/SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV7MVq
+      pac_type:
+        - MS-PAC
+        - PAD
+      auth_ind: otp
+      force: no
+      requires_pre_auth: yes
+      ok_as_delegate: no
+      ok_to_auth_as_delegate: no
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service is present, again
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ test_host }}"
+      certificate:
+        - MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQxMDhaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+XVVGFYpHVkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJzMLJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmjfMXp0nIToTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn0vQINt55YoEd1s4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpcxj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1UdDgQWBBRV0j7JYukuH/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+QeNlRLXDlEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs5y1Zp0WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqicuPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkGwIt1OH2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyEiaio3693l6noobyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj9uKQ5GTWRh59VnRBVC/SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV7MVq
+      pac_type:
+        - MS_PAC
+        - PAD
+      auth_ind: otp
+      force: no
+      requires_pre_auth: yes
+      ok_as_delegate: no
+      ok_to_auth_as_delegate: no
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure service is disabled
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ test_host }}"
+      state: disabled
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service member certificate is present.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ test_host }}"
+      certificate:
+        - MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQxMDhaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+XVVGFYpHVkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJzMLJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmjfMXp0nIToTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn0vQINt55YoEd1s4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpcxj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1UdDgQWBBRV0j7JYukuH/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+QeNlRLXDlEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs5y1Zp0WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqicuPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkGwIt1OH2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyEiaio3693l6noobyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj9uKQ5GTWRh59VnRBVC/SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV7MVq
+      action: member
+      state: present
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service member certificate is present, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ test_host }}"
+      certificate:
+        - MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQxMDhaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+XVVGFYpHVkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJzMLJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmjfMXp0nIToTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn0vQINt55YoEd1s4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpcxj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1UdDgQWBBRV0j7JYukuH/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+QeNlRLXDlEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs5y1Zp0WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqicuPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkGwIt1OH2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyEiaio3693l6noobyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj9uKQ5GTWRh59VnRBVC/SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV7MVq
+      action: member
+      state: present
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure service multiple member certificates are present, with duplicate.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ test_host }}"
+      certificate:
+        - MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQxMDhaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+XVVGFYpHVkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJzMLJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmjfMXp0nIToTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn0vQINt55YoEd1s4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpcxj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1UdDgQWBBRV0j7JYukuH/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+QeNlRLXDlEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs5y1Zp0WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqicuPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkGwIt1OH2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyEiaio3693l6noobyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj9uKQ5GTWRh59VnRBVC/SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV7MVq
+        - MIIC/zCCAeegAwIBAgIURhps6LEteMDCdBrlVkWe4cgSh0YwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQyNDBaFw0zMDAyMDExNDQyNDBaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4W56H0VraEKGlCxSTS2PqnaD11shMjruexmholmTEtYPePPnQHpwiiZlgK7CPBIOdCn4hHH+hXQDg/TJRMjrde1VzD0pFRBUq6H25sy8oOlfD0bDXkncWn82SOJu2UJHeL7htQLRxW14VIAO2YO9zaXdophy6/csTAkFq1ls/vTBp73pnnYp8D7TgzBB6bb95OZBSHeCzPIH2FSCJ/W0j6bHw4i7uHu/jWx0o0LR152fSFFwk0Wrmp8HHb2083OlnSBgTM+BZDg9rB7jpLCsIGHWXbjG36jmRaZu5z4vq2FNomJ8PXkX7mwUfaft6z+px7UlhrwUxEVWIXOoUBYcJAgMBAAGjUzBRMB0GA1UdDgQWBBTttCQn5UaQi+N5WRnA7ZTQlkVfRTAfBgNVHSMEGDAWgBTttCQn5UaQi+N5WRnA7ZTQlkVfRTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBW3vRR5wEDztuLVrcQDojn1XB24OOqn4C6OJyz3FUxd4MQA8J2vKN4P2QXhY0oYsauFKhR5xfOaDUcK2TukAtFz1mxqm1ygUVQHbrs8lBeIi4hoMc76ODJ/V9GNY7N/y/5xtD7XlyTVT2tb6tc6tmv8e4497PTPspuHp9YbbvzdSI12JENDW4hKCOpR/Uv7mRcCT+c2iMJdUL3f3YOFsGBbxVdTPmuhL4My8qR/CtCNpN0gBsaxUKFAP+/1AvFbFDChFVDEEdD8PLznH5x8HLmA9/K5x/cXbgqESUqK13P53f1XYOfggKb1f7yqBAZRnTY82+k9Kn9qWOcnyxSuUtZ
+      action: member
+      state: present
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service member certificate is absent.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ test_host }}"
+      certificate:
+        - MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQxMDhaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+XVVGFYpHVkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJzMLJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmjfMXp0nIToTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn0vQINt55YoEd1s4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpcxj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1UdDgQWBBRV0j7JYukuH/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+QeNlRLXDlEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs5y1Zp0WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqicuPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkGwIt1OH2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyEiaio3693l6noobyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj9uKQ5GTWRh59VnRBVC/SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV7MVq
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service member certificate is absent, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ test_host }}"
+      certificate:
+        - MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQxMDhaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+XVVGFYpHVkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJzMLJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmjfMXp0nIToTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn0vQINt55YoEd1s4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpcxj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1UdDgQWBBRV0j7JYukuH/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+QeNlRLXDlEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs5y1Zp0WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqicuPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkGwIt1OH2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyEiaio3693l6noobyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj9uKQ5GTWRh59VnRBVC/SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV7MVq
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure service member certificates are absent.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ test_host }}"
+      certificate:
+        - MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQxMDhaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+XVVGFYpHVkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJzMLJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmjfMXp0nIToTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn0vQINt55YoEd1s4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpcxj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1UdDgQWBBRV0j7JYukuH/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+QeNlRLXDlEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs5y1Zp0WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqicuPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkGwIt1OH2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyEiaio3693l6noobyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj9uKQ5GTWRh59VnRBVC/SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV7MVq
+        - MIIC/zCCAeegAwIBAgIURhps6LEteMDCdBrlVkWe4cgSh0YwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQyNDBaFw0zMDAyMDExNDQyNDBaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4W56H0VraEKGlCxSTS2PqnaD11shMjruexmholmTEtYPePPnQHpwiiZlgK7CPBIOdCn4hHH+hXQDg/TJRMjrde1VzD0pFRBUq6H25sy8oOlfD0bDXkncWn82SOJu2UJHeL7htQLRxW14VIAO2YO9zaXdophy6/csTAkFq1ls/vTBp73pnnYp8D7TgzBB6bb95OZBSHeCzPIH2FSCJ/W0j6bHw4i7uHu/jWx0o0LR152fSFFwk0Wrmp8HHb2083OlnSBgTM+BZDg9rB7jpLCsIGHWXbjG36jmRaZu5z4vq2FNomJ8PXkX7mwUfaft6z+px7UlhrwUxEVWIXOoUBYcJAgMBAAGjUzBRMB0GA1UdDgQWBBTttCQn5UaQi+N5WRnA7ZTQlkVfRTAfBgNVHSMEGDAWgBTttCQn5UaQi+N5WRnA7ZTQlkVfRTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBW3vRR5wEDztuLVrcQDojn1XB24OOqn4C6OJyz3FUxd4MQA8J2vKN4P2QXhY0oYsauFKhR5xfOaDUcK2TukAtFz1mxqm1ygUVQHbrs8lBeIi4hoMc76ODJ/V9GNY7N/y/5xtD7XlyTVT2tb6tc6tmv8e4497PTPspuHp9YbbvzdSI12JENDW4hKCOpR/Uv7mRcCT+c2iMJdUL3f3YOFsGBbxVdTPmuhL4My8qR/CtCNpN0gBsaxUKFAP+/1AvFbFDChFVDEEdD8PLznH5x8HLmA9/K5x/cXbgqESUqK13P53f1XYOfggKb1f7yqBAZRnTY82+k9Kn9qWOcnyxSuUtZ
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service multiple member certificates is present.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ test_host }}"
+      certificate:
+        - MIIC/zCCAeegAwIBAgIUMNHIbn+hhrOVew/2WbkteisV29QwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMDAyMDQxNDQxMDhaFw0zMDAyMDExNDQxMDhaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+XVVGFYpHVkcDfVnNInE1Y/pFciegdzqTjMwUWlRL4Zt3u96GhaMLRbtk+OfEkzLUAhWBOwEraELJzMLJOMvjYF3C+TiGO7dStFLikZmccuSsSIXjnzIPwBXa8KvgRVRyGLoVvGbLJvmjfMXp0nIToTx/i74KF9S++WEes9H5ErJ99CDhLKFgq0amnvsgparYXhypHaRLnikn0vQINt55YoEd1s4KrvEcD2VdZkIMPbLRu2zFvMprF3cjQQG4LT9ggfEXNIPZ1nQWAnAsu7OJEkNF+E4Mkmpcxj9aGUVt5bsq1D+Tzj3GsidSX0nSNcZ2JltXRnL/5v63g5cZyE+nAgMBAAGjUzBRMB0GA1UdDgQWBBRV0j7JYukuH/r/t9+QeNlRLXDlEDAfBgNVHSMEGDAWgBRV0j7JYukuH/r/t9+QeNlRLXDlEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgVy1+1kNwHs5y1Zp0WjMWGCJC6/zw7FDG4OW5r2GJiCXZYdJ0UonY9ZtoVLJPrp2/DAv1m5DtnDhBYqicuPgLzEkOS1KdTi20Otm/J4yxLLrZC5W4x0XOeSVPXOJuQWfwQ5pPvKkn6WxYUYkGwIt1OH2nSMngkbami3CbSmKZOCpgQIiSlQeDJ8oGjWFMLDymYSHoVOIXHwNoooyEiaio3693l6noobyGv49zyCVLVR1DC7i6RJ186ql0av+D4vPoiF5mX7+sKC2E8xEj9uKQ5GTWRh59VnRBVC/SiMJ/H78tJnBAvoBwXxSEvj8Z3Kjm/BQqZfv4IBsA5yqV7MVq
+      action: member
+      state: present
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service is disabled
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ test_host }}"
+      state: disabled
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service is disabled, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ test_host }}"
+      state: disabled
+    register: result
+    failed_when: result.changed
+
+  # cleanup
+  - name: Ensure services are absent.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ test_host }}"
+      state: absent
+
+  - name: Ensure host is absent
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name: "{{ test_host }}"
+      update_dns: yes
+      state: absent
diff --git a/tests/service/test_service.yml b/tests/service/test_service.yml
new file mode 100644
index 0000000000000000000000000000000000000000..10d1285a372402de92a0660914fc7ac29bad0fed
--- /dev/null
+++ b/tests/service/test_service.yml
@@ -0,0 +1,536 @@
+# This test uses skip_host_check, so it will fail if not using
+# FreeIPA version 4.7.0 or later.
+#
+# To test against earlier versions, use test_without_skip_host_check.yml.
+#
+# This test define 6 hosts:
+#    - www.ansible.com: a host with a DNS setup (external), not present in IPA
+#    - no.idontexist.info: a host without DNS and not present in IPA.
+#    - svc.ihavenodns.inf: a host without DNS, but present in IPA.
+#    - svc_fqdn: a host with DNS and present in IPA.
+#    - host1_fqdn and host2_fqdn: used for member actions only.
+#
+---
+- name: Test service
+  hosts: ipaserver
+  become: yes
+
+  tasks:
+  # setup
+  - name: Get Domain from server name
+    set_fact:
+      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
+    when: ipaserver_domain is not defined
+
+  - name: Set host1, host2 and svc hosts fqdn
+    set_fact:
+      host1_fqdn: "{{ 'host1.' + ipaserver_domain }}"
+      host2_fqdn: "{{ 'host2.' + ipaserver_domain }}"
+      svc_fqdn: "{{ 'svc.' + ipaserver_domain }}"
+
+  - name: Host absent
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - www.ansible.com
+      - no.idontexist.info
+      - svc.ihavenodns.info
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      - "{{ svc_fqdn }}"
+      update_dns: no
+      state: absent
+
+  - name: Get IPv4 address prefix from server node
+    set_fact:
+      ipv4_prefix: "{{ ansible_default_ipv4.address.split('.')[:-1] |
+                       join('.') }}"
+
+  - name: Add hosts for tests.
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      hosts:
+          - name: "{{ host1_fqdn }}"
+            force: yes
+          - name: "{{ host2_fqdn }}"
+            force: yes
+          - name: "{{ svc_fqdn }}"
+            ip_address: "{{ ipv4_prefix + '.201' }}"
+          - name: svc.ihavenodns.info
+            force: yes
+
+  - name: Ensure testing user user01 is present.
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      name: user01
+      first: user01
+      last: last
+
+  - name: Ensure testing user user02 is present.
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      name: user02
+      first: user02
+      last: last
+
+  - name: Ensure testing group group01 is present.
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: group01
+
+  - name: Ensure testing group group02 is present.
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: group02
+
+  - name: Ensure testing hostgroup hostgroup01 is present.
+    ipahostgroup:
+      ipaadmin_password: SomeADMINpassword
+      name: hostgroup01
+
+  - name: Ensure testing hostgroup hostgroup02 is present.
+    ipahostgroup:
+      ipaadmin_password: SomeADMINpassword
+      name: hostgroup02
+
+  - name: Ensure services are absent.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - "HTTP/{{ svc_fqdn }}"
+      - HTTP/www.ansible.com
+      - HTTP/svc.ihavenodns.info
+      - HTTP/no.idontexist.info
+      state: absent
+
+  # tests
+  - name: Ensure service is present
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      pac_type:
+        - MS-PAC
+        - PAD
+      auth_ind: otp
+      skip_host_check: no
+      force: no
+      requires_pre_auth: yes
+      ok_as_delegate: no
+      ok_to_auth_as_delegate: no
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service is present, again
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      pac_type:
+        - MS_PAC
+        - PAD
+      auth_ind: otp
+      skip_host_check: no
+      force: no
+      requires_pre_auth: yes
+      ok_as_delegate: no
+      ok_to_auth_as_delegate: no
+    register: result
+    failed_when: result.changed
+
+  - name: Modify service.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      pac_type: NONE
+      ok_as_delegate: yes
+      ok_to_auth_as_delegate: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Modify service, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      pac_type: NONE
+      ok_as_delegate: yes
+      ok_to_auth_as_delegate: yes
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure service is present, without host object.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/www.ansible.com
+      skip_host_check: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service is present, without host object, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/www.ansible.com
+      skip_host_check: yes
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure service is present, with host not in DNS.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/svc.ihavenodns.info
+      skip_host_check: no
+      force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service is present, with host not in DNS, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/svc.ihavenodns.info
+      skip_host_check: no
+      force: yes
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure service is present, whithout host object and with host not in DNS.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/no.idontexist.info
+      skip_host_check: yes
+      force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service is present, whithout host object and with host not in DNS, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/no.idontexist.info
+      skip_host_check: yes
+      force: yes
+    register: result
+    failed_when: result.changed
+
+  - name: Principal host/test.example.com present in service.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      principal:
+        - host/test.example.com
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Principal host/test.example.com present in service, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      principal:
+        - host/test.example.com
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Principal host/test.example.com absent in service.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      principal:
+        - host/test.example.com
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Principal host/test.example.com absent in service, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      principal:
+        - host/test.example.com
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure host can manage service.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure host can manage service, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      host: "{{ host1_fqdn }}"
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure host cannot manage service.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure host cannot manage service, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_create_keytab present for users, groups, hosts and hostgroups.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_create_keytab_user:
+      - user01
+      - user02
+      allow_create_keytab_group:
+      - group01
+      - group02
+      allow_create_keytab_host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      allow_create_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_create_keytab present for users, groups, hosts and hostgroups, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_create_keytab_user:
+      - user01
+      - user02
+      allow_create_keytab_group:
+      - group01
+      - group02
+      allow_create_keytab_host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      allow_create_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_create_keytab absent for users, groups, hosts and hostgroups.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_create_keytab_user:
+      - user01
+      - user02
+      allow_create_keytab_group:
+      - group01
+      - group02
+      allow_create_keytab_host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      allow_create_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_create_keytab absent for users, groups, hosts and hostgroups, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_create_keytab_user:
+      - user01
+      - user02
+      allow_create_keytab_group:
+      - group01
+      - group02
+      allow_create_keytab_host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      allow_create_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_retrieve_keytab present for users, groups, hosts and hostgroups
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_retrieve_keytab_user:
+      - user01
+      - user02
+      allow_retrieve_keytab_group:
+      - group01
+      - group02
+      allow_retrieve_keytab_host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_retrieve_keytab present for users, groups, hosts and hostgroups, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_retrieve_keytab_user:
+      - user01
+      - user02
+      allow_retrieve_keytab_group:
+      - group01
+      - group02
+      allow_retrieve_keytab_host:
+      - "{{ host1_fqdn }}"
+      - host02.exampl "{{ groups.ipaserver[0] }}"e.com
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_retrieve_keytab absent for users, groups, hosts and hostgroups.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_retrieve_keytab_user:
+      - user01
+      - user02
+      allow_retrieve_keytab_group:
+      - group01
+      - group02
+      allow_retrieve_keytab_host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_retrieve_keytab absent for users, groups, hosts and hostgroups, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_retrieve_keytab_user:
+      - user01
+      - user02
+      allow_retrieve_keytab_group:
+      - group01
+      - group02
+      allow_retrieve_keytab_host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  #
+  - name: Ensure service is absent
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service is absent, again
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  # cleanup
+
+  - name: Ensure services are absent.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - "HTTP/{{ svc_fqdn }}"
+      - HTTP/www.ansible.com
+      - HTTP/svc.ihavenodns.info
+      - HTTP/no.idontexist.local
+      state: absent
+
+  - name: Ensure host "{{ svc_fqdn }}" is absent
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name: "{{ svc_fqdn }}"
+      update_dns: yes
+      state: absent
+
+  - name: Ensure host is absent
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      - www.ansible.com
+      - svc.ihavenodns.info
+      update_dns: no
+      state: absent
+
+  - name: Ensure testing users are absent.
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - user01
+      - user02
+      state: absent
+
+  - name: Ensure testing groups are absent.
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - group01
+      - group02
+      state: absent
+
+  - name: Ensure testing hostgroup hostgroup01 is absent.
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - hostgroup01
+      state: absent
+
+  - name: Ensure testing hostgroup hostgroup02 is absent.
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - hostgroup02
+      state: absent
diff --git a/tests/service/test_service_without_skip_host_check.yml b/tests/service/test_service_without_skip_host_check.yml
new file mode 100644
index 0000000000000000000000000000000000000000..147da0c97d0bd21f1fa4f7def9585a16254cb69a
--- /dev/null
+++ b/tests/service/test_service_without_skip_host_check.yml
@@ -0,0 +1,476 @@
+---
+- name: Test service without using option skip_host_check
+  hosts: ipaserver
+  become: yes
+
+  tasks:
+  # setup
+  - name: Get Domain from server name
+    set_fact:
+      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
+    when: ipaserver_domain is not defined
+
+  - name: Set host1, host2 and svc hosts fqdn
+    set_fact:
+      host1_fqdn: "{{ 'host1.' + ipaserver_domain }}"
+      host2_fqdn: "{{ 'host2.' + ipaserver_domain }}"
+      svc_fqdn: "{{ 'svc.' + ipaserver_domain }}"
+
+  - name: Host absent
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - svc.ihavenodns.info
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      - "{{ svc_fqdn }}"
+      update_dns: yes
+      state: absent
+
+  - name: Get IPv4 address prefix from server node
+    set_fact:
+      ipv4_prefix: "{{ ansible_default_ipv4.address.split('.')[:-1] |
+                       join('.') }}"
+
+  - name: Add hosts for tests.
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        ip_address: "{{ ipv4_prefix + '.201' }}"
+        update_dns: yes
+      - name: "{{ host2_fqdn }}"
+        ip_address: "{{ ipv4_prefix + '.202' }}"
+        update_dns: yes
+      - name: "{{ svc_fqdn }}"
+        ip_address: "{{ ipv4_prefix + '.203' }}"
+        update_dns: yes
+      - name: svc.ihavenodns.info
+        update_dns: no
+        force: yes
+
+  - name: Ensure testing user user01 is present.
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      name: user01
+      first: user01
+      last: last
+
+  - name: Ensure testing user user02 is present.
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      name: user02
+      first: user02
+      last: last
+
+  - name: Ensure testing group group01 is present.
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: group01
+
+  - name: Ensure testing group group02 is present.
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: group02
+
+  - name: Ensure testing hostgroup hostgroup01 is present.
+    ipahostgroup:
+      ipaadmin_password: SomeADMINpassword
+      name: hostgroup01
+
+  - name: Ensure testing hostgroup hostgroup02 is present.
+    ipahostgroup:
+      ipaadmin_password: SomeADMINpassword
+      name: hostgroup02
+
+  - name: Ensure services are absent.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - "HTTP/{{ svc_fqdn }}"
+      - HTTP/svc.ihavenodns.info
+      state: absent
+
+  # tests
+  - name: Ensure service is present
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      pac_type:
+        - MS-PAC
+        - PAD
+      auth_ind: otp
+      force: no
+      requires_pre_auth: yes
+      ok_as_delegate: no
+      ok_to_auth_as_delegate: no
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service is present, again
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      pac_type:
+        - MS_PAC
+        - PAD
+      auth_ind: otp
+      force: no
+      requires_pre_auth: yes
+      ok_as_delegate: no
+      ok_to_auth_as_delegate: no
+    register: result
+    failed_when: result.changed
+
+  - name: Modify service.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      pac_type: NONE
+      ok_as_delegate: yes
+      ok_to_auth_as_delegate: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Modify service, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      pac_type: NONE
+      ok_as_delegate: yes
+      ok_to_auth_as_delegate: yes
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure service is present, with host not in DNS.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/svc.ihavenodns.info
+      force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service is present, with host not in DNS, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: HTTP/svc.ihavenodns.info
+      force: yes
+    register: result
+    failed_when: result.changed
+
+  - name: Principal host/test.example.com present in service.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      principal:
+        - host/test.example.com
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Principal host/test.exabple.com present in service, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      principal:
+        - host/test.example.com
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Principal host/test.example.com absent in service.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      principal:
+        - host/test.example.com
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Principal host/test.example.com absent in service, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      principal:
+        - host/test.example.com
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure host can manage service.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure host can manage service, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      host: "{{ host1_fqdn }}"
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure host cannot manage service.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure host cannot manage service, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_create_keytab present for users, groups, hosts and hostgroups.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_create_keytab_user:
+      - user01
+      - user02
+      allow_create_keytab_group:
+      - group01
+      - group02
+      allow_create_keytab_host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      allow_create_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_create_keytab present for users, groups, hosts and hostgroups, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_create_keytab_user:
+      - user01
+      - user02
+      allow_create_keytab_group:
+      - group01
+      - group02
+      allow_create_keytab_host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      allow_create_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_create_keytab absent for users, groups, hosts and hostgroups.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_create_keytab_user:
+      - user01
+      - user02
+      allow_create_keytab_group:
+      - group01
+      - group02
+      allow_create_keytab_host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      allow_create_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_create_keytab absent for users, groups, hosts and hostgroups, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_create_keytab_user:
+      - user01
+      - user02
+      allow_create_keytab_group:
+      - group01
+      - group02
+      allow_create_keytab_host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      allow_create_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_retrieve_keytab present for users, groups, hosts and hostgroups
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_retrieve_keytab_user:
+      - user01
+      - user02
+      allow_retrieve_keytab_group:
+      - group01
+      - group02
+      allow_retrieve_keytab_host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_retrieve_keytab present for users, groups, hosts and hostgroups, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_retrieve_keytab_user:
+      - user01
+      - user02
+      allow_retrieve_keytab_group:
+      - group01
+      - group02
+      allow_retrieve_keytab_host:
+      - "{{ host1_fqdn }}"
+      - host02.exampl "{{ groups.ipaserver[0] }}"e.com
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_retrieve_keytab absent for users, groups, hosts and hostgroups.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_retrieve_keytab_user:
+      - user01
+      - user02
+      allow_retrieve_keytab_group:
+      - group01
+      - group02
+      allow_retrieve_keytab_host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Service "HTTP/{{ svc_fqdn }}" members allow_retrieve_keytab absent for users, groups, hosts and hostgroups, again.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      allow_retrieve_keytab_user:
+      - user01
+      - user02
+      allow_retrieve_keytab_group:
+      - group01
+      - group02
+      allow_retrieve_keytab_host:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  #
+  - name: Ensure service is absent
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure service is absent, again
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name: "HTTP/{{ svc_fqdn }}"
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  # cleanup
+
+  - name: Ensure services are absent.
+    ipaservice:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - "HTTP/{{ svc_fqdn }}"
+      - HTTP/svc.ihavenodns.info
+      state: absent
+
+  - name: Ensure host is absent
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - "{{ svc_fqdn }}"
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      - svc.ihavenodns.info
+      state: absent
+
+  - name: Ensure testing users are absent.
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - user01
+      - user02
+      state: absent
+
+  - name: Ensure testing groups are absent.
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - group01
+      - group02
+      state: absent
+
+  - name: Ensure testing hostgroup hostgroup01 is absent.
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - hostgroup01
+      state: absent
+
+  - name: Ensure testing hostgroup hostgroup02 is absent.
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name:
+      - hostgroup02
+      state: absent