#!/usr/bin/python # Copyright: (c) 2020, Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import (absolute_import, division, print_function) __metaclass__ = type DOCUMENTATION = r''' --- module: httptester_kinit short_description: Get Kerberos ticket description: Get Kerberos ticket using kinit non-interactively. options: username: description: The username to get the ticket for. required: true type: str password: description: The password for I(username). required; true type: str author: - Ansible Project ''' EXAMPLES = r''' # ''' RETURN = r''' # ''' import contextlib import errno import os import subprocess from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.text.converters import to_bytes, to_text try: import configparser except ImportError: import ConfigParser as configparser @contextlib.contextmanager def env_path(name, value, default_value): """ Adds a value to a PATH-like env var and preserve the existing value if present. """ orig_value = os.environ.get(name, None) os.environ[name] = '%s:%s' % (value, orig_value or default_value) try: yield finally: if orig_value: os.environ[name] = orig_value else: del os.environ[name] @contextlib.contextmanager def krb5_conf(module, config): """ Runs with a custom krb5.conf file that extends the existing config if present. """ if config: ini_config = configparser.ConfigParser() for section, entries in config.items(): ini_config.add_section(section) for key, value in entries.items(): ini_config.set(section, key, value) config_path = os.path.join(module.tmpdir, 'krb5.conf') with open(config_path, mode='wt') as config_fd: ini_config.write(config_fd) with env_path('KRB5_CONFIG', config_path, '/etc/krb5.conf'): yield else: yield def main(): module_args = dict( username=dict(type='str', required=True), password=dict(type='str', required=True, no_log=True), ) module = AnsibleModule( argument_spec=module_args, required_together=[('username', 'password')], ) # Heimdal has a few quirks that we want to paper over in this module # 1. KRB5_TRACE does not work in any released version (<=7.7), we need to use a custom krb5.config to enable it # 2. When reading the password it reads from the pty not stdin by default causing an issue with subprocess. We # can control that behaviour with '--password-file=STDIN' # Also need to set the custom path to krb5-config and kinit as FreeBSD relies on the newer Heimdal version in the # port package. sysname = os.uname()[0] prefix = '/usr/local/bin/' if sysname == 'FreeBSD' else '' is_heimdal = sysname in ['Darwin', 'FreeBSD'] # Debugging purposes, get the Kerberos version. On platforms like OpenSUSE this may not be on the PATH. try: process = subprocess.Popen(['%skrb5-config' % prefix, '--version'], stdout=subprocess.PIPE) stdout, stderr = process.communicate() version = to_text(stdout) except OSError as e: if e.errno != errno.ENOENT: raise version = 'Unknown (no krb5-config)' kinit_args = ['%skinit' % prefix] config = {} if is_heimdal: kinit_args.append('--password-file=STDIN') config['logging'] = {'krb5': 'FILE:/dev/stdout'} kinit_args.append(to_text(module.params['username'], errors='surrogate_or_strict')) with krb5_conf(module, config): # Weirdly setting KRB5_CONFIG in the modules environment block does not work unless we pass it in explicitly. # Take a copy of the existing environment to make sure the process has the same env vars as ours. Also set # KRB5_TRACE to output and debug logs helping to identify problems when calling kinit with MIT. kinit_env = os.environ.copy() kinit_env['KRB5_TRACE'] = '/dev/stdout' process = subprocess.Popen(kinit_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=kinit_env) stdout, stderr = process.communicate(to_bytes(module.params['password'], errors='surrogate_or_strict') + b'\n') rc = process.returncode module.exit_json(changed=True, stdout=to_text(stdout), stderr=to_text(stderr), rc=rc, version=version) if __name__ == '__main__': main()