diff --git a/library/ipajoin.py b/library/ipajoin.py
new file mode 100644
index 0000000000000000000000000000000000000000..55f91a8e7d56fdafca864a913ad576bdec0b3f9f
--- /dev/null
+++ b/library/ipajoin.py
@@ -0,0 +1,246 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Thomas Woerner <twoerner@redhat.com>
+#
+# Based on ipa-client-install code
+#
+# Copyright (C) 2017  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',
+                    'status': ['preview'],
+                    'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: ipajoin
+short description: Join a machine to an IPA realm and get a keytab for the host service principal
+description:
+  Join a machine to an IPA realm and get a keytab for the host service principal
+options:
+  servers:
+    description: The FQDN of the IPA servers to connect to.
+    required: false
+  basedn:
+    description: The basedn of the IPA server (of the form dc=example,dc=com).
+    required: false
+  realm:
+    description: The Kerberos realm of an existing IPA deployment.
+    required: true
+  kdc:
+    description:
+    required: true
+  hostname:
+    description: The hostname of the machine to join (FQDN).
+    required: false
+  force_join:
+    description: Force enrolling the host even if host entry exists.
+    required: false
+  password:
+    description: The password to use if not using Kerberos to authenticate.
+    required: false
+author:
+    - Thomas Woerner
+'''
+
+EXAMPLES = '''
+'''
+
+RETURN = '''
+'''
+
+import os
+import tempfile
+import gssapi
+from ansible.module_utils.basic import AnsibleModule
+from ipalib import errors
+from ipaplatform.paths import paths
+from ipapython.dn import DN
+from ipalib.install import sysrestore
+from ipalib.install.kinit import kinit_keytab, kinit_password
+from ipaclient.install.client import configure_krb5_conf, get_ca_certs, SECURE_PATH, CCACHE_FILE
+from ipapython.ipautil import realm_to_suffix, run
+
+import logging
+logger = logging.getLogger("ipa-client-install")
+
+def main():
+    module = AnsibleModule(
+        argument_spec = dict(
+            servers=dict(required=True, type='list'),
+            basedn=dict(required=True),
+            realm=dict(required=True),
+            kdc=dict(required=True),
+            hostname=dict(required=True),
+            domain=dict(required=True),
+            force_join=dict(required=False, type='bool'),
+            principal=dict(required=False),
+            password=dict(required=False),
+            keytab=dict(required=False),
+            ca_certs_file=dict(required=False),
+            kinit_attempts=dict(required=False, type='int'),
+        ),
+        # required_one_of = ( [ '', '' ] ),
+        supports_check_mode = True,
+    )
+
+    module._ansible_debug = True
+    servers = module.params.get('servers')
+    basedn = module.params.get('basedn')
+    realm = module.params.get('realm')
+    kdc = module.params.get('kdc')
+    hostname = module.params.get('hostname')
+    domain = module.params.get('hostname')
+    force_join = module.params.get('force_join')
+    principal = module.params.get('principal')
+    password = module.params.get('password')
+    keytab = module.params.get('keytab')
+    ca_certs_file = module.params.get('ca_certs_file')
+    kinit_attempts = module.params.get('kinit_attempts')
+
+    client_domain = hostname[hostname.find(".")+1:]
+    nolog = tuple()
+    env = {'PATH': SECURE_PATH}
+    fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
+    host_principal = 'host/%s@%s' % (hostname, realm)
+    sssd = True
+
+    class Object(object):
+        pass
+    options = Object()
+    options.ca_cert_file = ca_certs_file
+    options.unattended = True
+    options.principal = principal
+    options.password = password
+    options.force = False
+
+    try:
+        (krb_fd, krb_name) = tempfile.mkstemp()
+        os.close(krb_fd)
+        configure_krb5_conf(
+            cli_realm=realm,
+            cli_domain=domain,
+            cli_server=servers,
+            cli_kdc=kdc,
+            dnsok=False,
+            filename=krb_name,
+            client_domain=client_domain,
+            client_hostname=hostname,
+            configure_sssd=sssd,
+            force=False)
+        env['KRB5_CONFIG'] = krb_name
+        ccache_dir = tempfile.mkdtemp(prefix='krbcc')
+        ccache_name = os.path.join(ccache_dir, 'ccache')
+        join_args = [paths.SBIN_IPA_JOIN,
+                     "-s", servers[0],
+                     "-b", str(realm_to_suffix(realm)),
+                     "-h", hostname]
+        if force_join:
+            join_args.append("-f")
+        if principal:
+            module.log("before kinit_password")
+            if principal.find('@') == -1:
+                principal = '%s@%s' % (principal, realm)
+            try:
+                kinit_password(principal, password, ccache_name,
+                               config=krb_name)
+            except RuntimeError as e:
+                module.fail_json(
+                    msg="Kerberos authentication failed: {}".format(e))
+        elif keytab:
+            join_args.append("-f")
+            if os.path.exists(keytab):
+                try:
+                    kinit_keytab(host_principal,
+                                 keytab,
+                                 ccache_name,
+                                 config=krb_name,
+                                 attempts=kinit_attempts)
+                except gssapi.exceptions.GSSError as e:
+                    module.fail_json(
+                        msg="Kerberos authentication failed: {}".format(e))
+            else:
+                module.fail_json(
+                    msg="Keytab file could not be found: {}".format(keytab))
+
+        elif password:
+            nolog = (password,)
+            join_args.append("-w")
+            join_args.append(password)
+
+        env['KRB5CCNAME'] = os.environ['KRB5CCNAME'] = ccache_name
+        # Get the CA certificate
+        try:
+            os.environ['KRB5_CONFIG'] = env['KRB5_CONFIG']
+            get_ca_certs(fstore, options, servers[0], basedn, realm)
+            del os.environ['KRB5_CONFIG']
+        except errors.FileError as e:
+            module.fail_json(msg='%s' % e)
+        except Exception as e:
+            module.fail_json(msg="Cannot obtain CA certificate\n%s" % e)
+
+        # Now join the domain
+        result = run(
+            join_args, raiseonerr=False, env=env, nolog=nolog,
+            capture_error=True)
+        stderr = result.error_output
+
+        if result.returncode != 0:
+            module.fail_json(msg="Joining realm failed: %s" % stderr)
+        else:
+            module.log("Enrolled in IPA realm %s" % realm)
+
+        start = stderr.find('Certificate subject base is: ')
+        if start >= 0:
+            start = start + 29
+            subject_base = stderr[start:]
+            subject_base = subject_base.strip()
+            subject_base = DN(subject_base)
+
+        # Obtain the TGT. We do it with the temporary krb5.conf, so that
+        # only the KDC we're installing under is contacted.
+        # Other KDCs might not have replicated the principal yet.
+        # Once we have the TGT, it's usable on any server.
+        try:
+            kinit_keytab(host_principal, paths.KRB5_KEYTAB, CCACHE_FILE,
+                         config=krb_name,
+                         attempts=kinit_attempts)
+            env['KRB5CCNAME'] = os.environ['KRB5CCNAME'] = CCACHE_FILE
+        except gssapi.exceptions.GSSError as e:
+            # failure to get ticket makes it impossible to login and bind
+            # from sssd to LDAP, abort installation and rollback changes
+            module.fail_json(msg="Failed to obtain host TGT: %s" % e)
+
+    finally:
+        try:
+            os.remove(krb_name)
+        except OSError:
+            module.fail_json(msg="Could not remove %s" % krb_name)
+        try:
+            os.rmdir(ccache_dir)
+        except OSError:
+            pass
+        try:
+            os.remove(krb_name + ".ipabkp")
+        except OSError:
+            module.fail_json(msg="Could not remove %s.ipabkp" % krb_name)
+
+    module.exit_json(changed=True),
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/ipaclient/tasks/install.yml b/roles/ipaclient/tasks/install.yml
index 22152e098c0dfe99e11784abe4b4cd6edaf036d9..c332712aabaf77e31e2bdd03ce4ea717fc62dd4b 100644
--- a/roles/ipaclient/tasks/install.yml
+++ b/roles/ipaclient/tasks/install.yml
@@ -40,6 +40,20 @@
 
   when: ipaclient_password is not defined and ipaclient_keytab is not defined
 
+- name: Install - Join IPA
+  ipajoin:
+    servers: "{{ ipadiscovery.servers | default(omit) }}"
+    basedn: "{{ ipadiscovery.basedn | default(omit) }}"
+    realm: "{{ ipadiscovery.realm | default(omit) }}"
+    kdc: "{{ ipadiscovery.kdc | default(omit) }}"
+    hostname: "{{ ipadiscovery.hostname }}"
+    domain: "{{ ipadiscovery.domain | default(omit) }}"
+    force_join: "{{ ipaclient_force_join | default(omit) }}"
+    principal: "{{ ipaclient_principal | default(omit) }}"
+    password: "{{ ipaclient_password | default(omit) }}"
+    keytab: "{{ ipaclient_keytab | default(omit) }}"
+    #ca_certs_file: "{{ ipaclient_ca_certs_file | default(omit) }}"
+    kinit_attempts: "{{ ipaclient_kinit_attempts | default(omit) }}"
 
 - name: Install - Configure IPA client
   ipaclient: