Files
ansible-freeipa/library/ipaclient.py
Florence Blanc-Renaud 38d7223376 Modify ipahost module: the authentication is done locally on the controller
node and the credential cache is copied to the managed node

ipahost module is also using facts gathered from the server to find the
domain and realm.
2017-08-10 16:54:44 +02:00

303 lines
8.8 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Authors:
# Florence Blanc-Renaud <frenaud@redhat.com>
#
# 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: ipaclient
short description: Configures a client machine as IPA client
description:
Configures a client machine to use IPA for authentication and
identity services.
The enrollment requires one authentication method among the 3 following:
- Kerberos principal and password (principal/password)
- Kerberos keytab file (keytab)
- One-Time-Password (otp)
options:
state:
description: the client state
required: false
default: present
choices: [ "present", "absent"]
domain:
description: The primary DNS domain of an existing IPA deployment.
required: false
realm:
description: The Kerberos realm of an existing IPA deployment.
required: false
server:
description: The FQDN of the IPA server to connect to.
required: false
principal:
description: The authorized kerberos principal used to join the IPA realm.
required: false
default: admin
password:
description: The password for the kerberos principal.
required: false
keytab:
description: The pathto a backed-up host keytab from previous enrollment.
required: false
otp:
description: The One-Time-Password used to join the IPA realm.
required: false
extr_args:
description: The list of extra arguments to provide to ipa-client-install.
required: false
type: list
author:
- Florence Blanc-Renaud
'''
EXAMPLES = '''
# Example from Ansible Playbooks
# Unenroll client
- ipaclient:
state: absent
# Enroll client using admin credentials, with auto-discovery
- ipaclient:
principal: admin
password: MySecretPassword
extraargs: [ '--no-ntp', '--kinit-attempts=5']
# Enroll client using admin credentials, with specified domain and
# autodiscovery of the IPA server
- ipaclient:
principal: admin
password: MySecretPassword
domain: ipa.domain.com
extraargs: [ '--no-ntp', '--kinit-attempts=5']
# Enroll client using admin credentials, with specified server
- ipaclient:
principal: admin
password: MySecretPassword
domain: ipa.domain.com
server: ipaserver.ipa.domain.com
extraargs: [ '--no-ntp', '--kinit-attempts=5']
# Enroll client using One-Time-Password, with specified domain and realm
- ipaclient:
domain: ipa.domain.com
realm: IPA.DOMAIN.com
otp: 9Mn*Jm8z[%n]|:CJeu>Y~K
# Re-enroll client using keytab stored on the managed node
- ipaclient:
domain: ipa.domain.com
realm: IPA.DOMAIN.com
keytab: /path/to/host.keytab
'''
RETURN = '''
tbd
'''
import os
from six.moves.configparser import RawConfigParser
from ansible.module_utils.basic import AnsibleModule
from ipalib.install.sysrestore import SYSRESTORE_STATEFILE
from ipaplatform.paths import paths
def is_client_configured():
"""
Check if ipa client is configured.
IPA client is configured when /etc/ipa/default.conf exists and
/var/lib/ipa-client/sysrestore/sysrestore.state exists.
:returns: boolean
"""
return (os.path.isfile(paths.IPA_DEFAULT_CONF) and
os.path.isfile(os.path.join(paths.IPA_CLIENT_SYSRESTORE,
SYSRESTORE_STATEFILE)))
def get_ipa_conf():
"""
Return IPA configuration read from /etc/ipa/default.conf
:returns: dict containing key,value
"""
parser = RawConfigParser()
parser.read(paths.IPA_DEFAULT_CONF)
result = dict()
for item in ['basedn', 'realm', 'domain', 'server', 'host', 'xmlrpc_uri']:
if parser.has_option('global', item):
value = parser.get('global', item)
else:
value = None
if value:
result[item] = value
return result
def ensure_not_ipa_client(module):
"""
Module for client uninstallation
If IPA client is installed, calls ipa-client-install --uninstall -U
:param module: AnsibleModule
"""
# Check if IPA client is already configured
if not is_client_configured():
# Nothing to do
module.exit_json(changed=False)
# Client is configured
# If in check mode, do nothing but return changed=True
if module.check_mode:
module.exit_json(changed=True)
# Client is configured and we want to remove it
cmd = [
module.get_bin_path('ipa-client-install'),
"--uninstall",
"-U",
]
retcode, stdout, stderr = module.run_command(cmd)
if retcode != 0:
module.fail_json(msg="Failed to uninstall IPA client: %s" % stderr)
module.exit_json(changed=True)
def ensure_ipa_client(module):
"""
Module for client installation
If IPA client is not installed, calls ipa-client-install
:param module: AnsibleModule
"""
domain = module.params.get('domain')
realm = module.params.get('realm')
server = module.params.get('server')
principal = module.params.get('principal')
password = module.params.get('password')
keytab = module.params.get('keytab')
otp = module.params.get('otp')
extra_args = module.params.get('extra_args')
# Ensure that at least one auth method is specified
if not (password or keytab or otp):
module.fail_json(msg="At least one of password, keytab or otp "
"must be specified")
# Check if ipa client is already configured
if is_client_configured():
# Check that realm and domain match
current_config = get_ipa_conf()
if domain and domain != current_config.get('domain'):
return module.fail_json(msg="IPA client already installed "
"with a conflicting domain")
if realm and realm != current_config.get('realm'):
return module.fail_json(msg="IPA client already installed "
"with a conflicting realm")
# client is already configured and no inconsistency detected
return module.exit_json(changed=False, domain=domain, realm=realm)
# ipa client not installed
if module.check_mode:
# Do nothing, just return changed=True
return module.exit_json(changed=True)
cmd = [
module.get_bin_path("ipa-client-install"),
"-U",
]
if domain:
cmd.append("--domain")
cmd.append(domain)
if realm:
cmd.append("--realm")
cmd.append(realm)
if server:
cmd.append("--server")
cmd.append(server)
if password:
cmd.append("--password")
cmd.append(password)
cmd.append("--principal")
cmd.append(principal)
if keytab:
cmd.append("--keytab")
cmd.append(keytab)
cmd.append("-d")
if otp:
cmd.append("--password")
cmd.append(otp)
if extra_args:
for extra_arg in extra_args:
cmd.append(extra_arg)
retcode, stdout, stderr = module.run_command(cmd)
if retcode != 0:
module.fail_json(msg="Failed to install IPA client: %s" % stderr)
# If autodiscovery was used, need to read /etc/ipa/default.conf to
# find domain and realm
new_config = get_ipa_conf()
module.exit_json(changed=True,
domain=new_config.get('domain'),
realm=new_config.get('realm'))
def main():
module = AnsibleModule(
supports_check_mode=True,
argument_spec=dict(
state=dict(default='present', choices=['present', 'absent']),
domain=dict(required=False),
realm=dict(required=False),
server=dict(required=False),
principal=dict(default='admin'),
password=dict(required=False, no_log=True),
keytab=dict(required=False, type='path'),
otp=dict(required=False),
extra_args=dict(default=None, type='list')
),
)
module._ansible_debug = True
state = module.params.get('state')
if state == 'present':
ensure_ipa_client(module)
else:
ensure_not_ipa_client(module)
if __name__ == '__main__':
main()