URL: https://github.com/freeipa/freeipa/pull/215 Author: jumitche Title: #215: Add script to setup krb5 NFS exports Action: synchronized
To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/215/head:pr215 git checkout pr215
From 99c8c50dd7f1cf106b9480c1805339eb2382f18c Mon Sep 17 00:00:00 2001 From: Justin Mitchell <jumit...@redhat.com> Date: Tue, 8 Nov 2016 11:15:57 +0000 Subject: [PATCH 1/3] Add script to setup krb5 NFS exports --- client/Makefile.am | 1 + client/ipa-client-nfsexport | 814 ++++++++++++++++++++++++++++++++++++++++++++ freeipa.spec.in | 1 + 3 files changed, 816 insertions(+) create mode 100755 client/ipa-client-nfsexport diff --git a/client/Makefile.am b/client/Makefile.am index 30adafd..8996fd5 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -45,6 +45,7 @@ sbin_PROGRAMS = \ sbin_SCRIPTS = \ ipa-client-install \ ipa-client-automount \ + ipa-client-nfsexport \ ipa-certupdate \ $(NULL) diff --git a/client/ipa-client-nfsexport b/client/ipa-client-nfsexport new file mode 100755 index 0000000..ef47942 --- /dev/null +++ b/client/ipa-client-nfsexport @@ -0,0 +1,814 @@ +#!/usr/bin/python -E +# +# Configure an IPA/AD client system to serve Kerberos NFS4 +# +# Author: Justin Mitchell <jumit...@redhat.com> +# +# Copyright (C) 2016 Red Hat, Inc. +# +# 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/>. +# +# +## Clients must also do: +# ipa service-add nfs/client.mydomain +# ipa-getkeytab -s ipa.mydomain -p nfs/client.mydomain -k /etc/krb5.keytab +# systemctl start nfs-client.target +# optionally: ipa-client-automount + +from __future__ import print_function + +try: + import sys + import os + import time + import tempfile + import dns + import socket + import netaddr + import logging + import subprocess + import tempfile + import ConfigParser + import re + + from dns import resolver, rdatatype + from dns.exception import DNSException + from argparse import ArgumentParser + from subprocess import CalledProcessError, check_output, check_call + +except ImportError as e: + print("""\ +There was a problem importing one of the required Python modules. The +error was: + + %s +""" % e, file=sys.stderr) + sys.exit(1) + + +class Paths: + """Collection of pathnames and executables to use""" + IPA_CLI = "/usr/bin/ipa" + IPA_GETKEYTAB = "/usr/sbin/ipa-getkeytab" + KLIST = "/usr/bin/klist" + KINIT = "/usr/bin/kinit" + IPA_DEFAULT_CONF = "/etc/ipa/default.conf" + RESOLV_CONF = "/etc/resolv.conf" + EXPORTS = "/var/lib/nfs/etab" + KEYTAB = "/etc/krb5.keytab" + EXPORTSFILE = "/etc/exports.d/krb5.exports" + EXPORTFS = "/usr/sbin/exportfs" + SYSTEMCTL = "/usr/bin/systemctl" + IPACONFIG = "/etc/ipa/default.conf" + KRB5CONFIG = "/etc/krb5.conf" + DNF = "/usr/bin/dnf" + + +def parse_options(): + parser = ArgumentParser() + + parser.add_argument("--domain", dest="domain", help="domain name") + parser.add_argument("--server", dest="server", help="IPA server", action="append") + parser.add_argument("--export", dest="exports", help="NFS mount exports", action="append") + parser.add_argument("--realm", dest="realm", help="realm name") + parser.add_argument("--hostname", dest="hostname", help="The hostname of this machine (FQDN)") + parser.add_argument("--username", dest="username", help="Kerberos Username") + parser.add_argument("--force", action="store_true", + help="Perform actions even if unneccessary") + parser.add_argument("-v", "--verbose", help="Increase Verbosity", action="count") + parser.add_argument("--automount", dest="automount", default=None, action="store_true", + help="Configure mounts for automount use") + parser.add_argument("--noautomount", dest="automount", default=None, action="store_false", + help="Do not configure mounts for automount use") + + options = parser.parse_args() + + if options.verbose > 0: + logging.getLogger().setLevel(logging.DEBUG) + + return options + + +def have_keytab( hostname, service='host', realm=None ): + """Test if we have been configured for any realm by the existance + of a host key in the default keytab""" + + principal = '%s/%s' % (service, hostname) + if realm: + principal = '%s/%s@%s' % (service, hostname, realm.upper()) + + logging.debug("Checking for principal %s in keytab" % principal ) + + try: + out = subprocess.check_output([ Paths.KLIST, '-k'], stderr=devnull) + + if out.find(principal) != -1: + return True + + except CalledProcessError as e: + logging.debug("klist Error ret=%d %s" , e.returncode, e) + return False + + return False + +def get_hostname(): + """Get the system hostname""" + return socket.getfqdn() + +def valid_ip(addr): + """Test if {addr} is valid format for an IP address""" + return netaddr.valid_ipv4(addr) or netaddr.valid_ipv6(addr) + +def get_resolver_domains(): + """Extract any likely looking domains from resolv.conf""" + domains = [] + domain = None + try: + fp = open(Paths.RESOLV_CONF, 'r') + lines = fp.readlines() + fp.close() + + for line in lines: + if line.lower().startswith('domain'): + domain = line.split()[-1] + elif line.lower().startswith('search'): + domains += line.split()[1:] + except: + pass + + logging.debug(" resolv.conf search domains: " + str(domains)) + if domain: + logging.debug(" - resolv.conf domain: " + domain) + domains.append(domain) + return domains + + +def dns_search_srv(domain, srv_record_name, default_port): + """Search for SRV records in the domain""" + qname = '%s.%s' % (srv_record_name, domain) + try: + answers = resolver.query(qname, rdatatype.SRV) + except DNSException, e: + answers = [] + + logging.debug(" SRV query for " + qname ) + + servers = [] + for answer in answers: + logging.debug(" - target=" + str(answer.target) + " port=" + str(answer.port)) + host = str(answer.target).rstrip(".") + if not host: + continue; + + if default_port is not None and answer.port != default_port: + host = "%s:%s" % (host, str(answer.port)) + servers.append( host ) + if not answers: + logging.debug(" - No answers") + + return servers + + +def search_servers( domain ): + """Build a list of possible domain names, + then search it for LDAP servers """ + domains = [] + + if domain and not valid_ip(domain): + p = domain.find(".") + if p != -1: + domains.append( domain.lower() ) + + rd = get_resolver_domains() + for d in rd: + if d.lower() not in domains: + domains.append(d.lower()) + + logging.debug("LDAP Search Domain List: %s", domains) + + servers = [] + tried = set() + for d in domains: + if d in tried: + continue + tried.add(d) + + p = d.find(".") + while p != -1: + found = dns_search_srv(d, '_ldap._tcp', 389) + if found: + for f in found: + servers.append( f ) + break + d = d[p+1:] + p = d.find(".") + + return servers + +def search_realm(hostname): + """Search dns for kerberos TXT records""" + logging.debug("Searching DNS for Kerberos Realm...") + + domain = None + realm = None + if hostname and not valid_ip(hostname): + p = hostname.find(".") + if p != -1: + domain = hostname[p+1:] + + if not domain: + raise ValueError("bad hostname") + + qname = "_kerberos." + domain + if not qname.endswith('.'): + qname += '.' + + logging.debug(" TXT query for %s" , qname) + try: + answers = resolver.query(qname, rdatatype.TXT) + except DNSException, e: + raise + + for answer in answers: + logging.debug(" - Answer: %s" , answer.strings) + try: + return answer.strings[0] + except: + pass + + raise RuntimeError("Not found") + + +def user_input(prompt, default=None, allow_empty=True): + """Prompt the user for some input, with optional default value""" + + if isinstance(default, basestring): + ret = raw_input("%s [%s]: " % (prompt, default)) + if not ret.strip(): + return default + else: + return ret + elif isinstance(default, bool): + if default: + choice = "yes" + else: + choice = "no" + while True: + ret = raw_input("%s [%s]: " % (prompt, choice)) + if not ret.strip(): + return default + elif ret.lower()[0] == "y": + return True + elif ret.lower()[0] == "n": + return False + elif isinstance(default, int): + while True: + try: + ret = raw_input("%s [%s]: " % (prompt, choice)) + if not ret.strip(): + return default + ret = int(ret) + except ValueError: + pass + else: + return ret + else: + while True: + ret = raw_input("%s: " % prompt) + if allow_empty or (ret and ret.strip()): + return ret + +def search_exports(): + """Grab a list of all currently exported domains""" + exports = [] + try: + fp = open(Paths.EXPORTS, 'r') + lines = fp.readlines() + fp.close() + + logging.debug("Searching exports file %s", Paths.EXPORTS) + for line in lines: + if line.lower().startswith('#'): + continue + domain = line.split()[0] + if not domain in exports: + exports.append(domain) + return exports + except: + raise + +def krb5_fetchinfo(): + """Try and extract info from klist""" + try: + cmd = [ Paths.KLIST ] + ret = check_output(cmd) + matches = re.findall(r'Default principal: (.*)@(.*)\n', ret) + logging.debug("results: %s" % matches) + return dict( username=matches[0][0], realm=matches[0][1] ) + except: + logging.debug("klist extract failed: %s", sys.exc_info()[1]) + + +def krb5_valid(ccache=None): + """Test we have a TGT cached""" + try: + cmd = [ Paths.KLIST, '-s' ] + if ccache: + cmd += [ '-c', ccache ]; + ret = check_call(cmd) + if ret > 0: + logging.debug("No current valid keys found in ccache") + return False + else: + logging.debug("Found valid ccache") + return True + except: + logging.debug("klist error: %s", sys.exc_info()) + return False + +def krb5_init( username, realm=None, force=False, ccache=None): + """Test and login to realm""" + if krb5_valid(ccache) and not force: + return + + if realm: + principal = "%s@%s" % (username, realm) + else: + principal = username + + try: + cmd = [ Paths.KINIT ] + if ccache: + cmd += [ '-c', ccache ] + cmd += [ principal ] + ret = check_call(cmd, stdin=sys.stdin, stdout=sys.stdout) + if ret != 0: + logging.error("kinit returned %d", ret) + except CalledProcessError as e: + raise + + +def ipa_service_exists( hostname, service='nfs', realm=None): + """Ask IPA if this service principal exists""" + principal = '%s/%s' % ( service, hostname ) + try: + out = check_output([ Paths.IPA_CLI, 'service-show', principal ], stderr=devnull) + return True + + except CalledProcessError as e: + logging.debug("Failed to find service %s" % principal) + return False + +def ipa_service_add( hostname, service='nfs', realm=None, force=False): + """Create this service principal via IPA""" + principal = '%s/%s' % ( service, hostname ) + logging.debug("Adding service %s" % principal) + cmd = [ Paths.IPA_CLI, 'service-add', principal ] + if force: + cmd.append('--force') + try: + out = check_output( cmd ) + + except CalledProcessError as e: + logging.error("'%s' failed with retcode %d: %s" % (cmd[0], e.returncode, e.output)) + raise + +def fetch_keytab(hostname, service='nfs', server=None, keytab=None): + """Fetch a service key""" + principal = '%s/%s' % (service, hostname) + cmd = [ Paths.IPA_GETKEYTAB, '-p', principal ] + if server: + cmd += [ '-s', server ] + if keytab: + cmd += [ '-k', keytab ] + + try: + out = check_output(cmd) + except: + raise + + +def service_restart(service, force=False): + """Check if a systemd service is enabled, if needed enable and run it""" + + # Is it already enabled, or do we not care + enabled = False + if not force: + cmd = [ Paths.SYSTEMCTL, 'is-enabled', service ] + try: + ret = check_call(cmd, stdout=devnull, stderr=devnull) + if ret == 0: + enabled = True + except CalledProcessError as e: + pass + + # Enable it + if not enabled: + cmd = [ Paths.SYSTEMCTL, 'enable', service ] + try: + ret = check_call(cmd, stdout=devnull, stderr=devnull) + except CalledProcessError as e: + logging.error("Error enabling service %s" % service) + raise + + # run/restart it + if force: + cmd = [ Paths.SYSTEMCTL, 'restart', service ] + else: + cmd = [ Paths.SYSTEMCTL, 'try-restart', service ] + + try: + ret = check_call(cmd, stdout=devnull, stderr=devnull) + except CalledProcessError as e: + logging.error("Error restarting service %s" % service) + raise + + +def update_exports(): + """Update the exports with exportfs""" + cmd = [ Paths.EXPORTFS , '-a' ] + logging.debug("Updating exports...") + try: + ret = check_output( cmd ) + logging.debug(ret) + except: + raise + +def load_ipa_config(pathname): + """Parse an IPA config file and return a dict of the values we found""" + config = ConfigParser.ConfigParser() + + values = dict() + try: + config.read([pathname]) + except: + raise + + try: + values['realm'] = config.get('global', 'realm') + except: + pass + + try: + values['server'] = config.get('global', 'server') + except: + pass + + try: + values['hostname'] = config.get('global', 'host') + except: + pass + + if not values: + raise EOFError('Empty Config') + + return values + + +def mapadd( hostname, directory ): + parts = directory.rsplit('/', 1) + if parts[1]: + mapname = 'auto.%s' % parts[1] + else: + mapname = 'auto.%s' % directory + + logging.debug("Adding map '%s' %s:%s" % (mapname, hostname, directory)) + + try: + # Create the map + logging.debug("Create map '%s'" % mapname) + cmd = [ Paths.IPA_CLI, 'automountmap-add', 'default', mapname ] + ret = check_output(cmd, stderr=devnull) + + # set the directory and make a sub of auto.master + logging.debug("Add map directory '%s'" % directory) + cmd = [ Paths.IPA_CLI, 'automountkey-add', 'default', '--key', directory, '--info', mapname, 'auto.master' ] + ret = check_output(cmd, stderr=devnull) + + # Now set the mapping + logging.debug("Set map key '%s'" % hostname) + cmd = [ Paths.IPA_CLI, 'automountkey-add', 'default', '--key', '*', '--info', "-fstype=nfs4,rw,sec=krb5p,soft,rsize=8192,wsize=8192 %s:%s/&" % (hostname, directory), mapname ] + ret = check_output(cmd, stderr=devnull) + + except CalledProcessError as e: + logging.debug("Error creating map %s: %s " % (mapname, e) ) + raise + + + + +def main(): + env={"PATH":"/bin:/sbin:/usr/kerberos/bin:/usr/kerberos/sbin:/usr/bin:/usr/sbin"} + + tool = 'yum' + if os.path.exists( Paths.DNF ): + tool = 'dnf' + + # basic sanity checks first + if not os.path.exists( Paths.IPA_CLI ): + logging.error("%s not found. Try '%s install ipa-admintools' first" % (Paths.IPA_CLI, tool)) + sys.exit(1) + + if not os.path.exists( Paths.IPA_GETKEYTAB ): + logging.error("%s not found. Try '%s install ipa-client' first" % (Paths.IPA_GETKEYTAB, tool)) + sys.exit(1) + + if not os.path.exists( Paths.KLIST ): + logging.error("%s not found. Try '%s install krb5-workstation' first" % (Paths.KLIST, tool)) + sys.exit(1) + + if not os.path.exists( Paths.KINIT ): + logging.error("%s not found. Try '%s install krb5-workstation' first" % ( Paths.KINIT, tool)) + sys.exit(1) + + if not os.path.exists( Paths.EXPORTFS ): + logging.error("%s not found. Try '%s install nfs-utils' first" % ( Paths.EXPORTFS, tool)) + sys.exit(1) + + + + # Check for cmdline options + options = parse_options() + + # commandline provided options take precidence, so assign them first + hostname = options.hostname + realm = options.realm + username = options.username + servers = [] + exports = [] + + if options.server: + servers.append( options.server ) + + # Can we get hostname, username, realm, etc from the ipa config file ? + ipaconf = dict() + try: + ipaconf = load_ipa_config(Paths.IPACONFIG) + except: + logging.debug("load config failed: %s" % sys.exc_value); + pass + + # If we have them, try using the ipa config values next + if not hostname: + try: + hostname = ipaconf['hostname'] + except: + pass + + if not realm: + try: + realm = ipaconf['realm'] + except: + pass + + if not servers: + try: + newserver = ipaconf['server'] + servers.append( newserver ) + except: + pass + + + # still no luck with hostname, ask the system + if not hostname: + hostname = get_hostname() + + # All attempts have failed, give in and ask the user + if not hostname: + print("Unable to determine hostname, and not provided by --hostname") + hostname = user_input("Enter hostname", allow_empty=False) + + + # Check if we have a key cached for this hostname + # if not then we probably dont have IPA/AD setup yet + if not have_keytab(hostname, 'host'): + sys.exit("Host key not found. run ipa-client-install first ?") + + # We still don't know the realm, check in DNS + if not realm: + try: + realm = search_realm(hostname) + except: + pass + + # Maybe we have signed in already and that can tell us? + if not realm: + logging.debug("Checking klist for realm info") + try: + kinfo = krb5_fetchinfo() + realm = kinfo['realm'] + if not username: + username = kinfo['username'] + except: + pass + + # We cant find a realm so ask the user + if not realm: + domain = None + if hostname and not valid_ip(hostname): + p = hostname.find(".") + if p != -1: + domain = hostname[p+1:] + print("Unable to determine realm, and not provided by --realm") + realm = user_input("Kerberos Realm", allow_empty=False, default=domain.upper()) + + + # Not manual, check in DNS for it + if not servers: + logging.debug("Searching for IPA/LDAP servers...") + servers += search_servers( realm ) + + # still havent found it, demand one + if not servers: + print("Unable to determine IPA/LDAP server, and not provided by --server") + servers.append( user_input("IPA/LDAP Server", allow_empty=False) ) + + # Grab a list of what is already exported on this system + try: + exported = search_exports() + except: + pass + + # Has the user given a manual list of directories to export + if options.exports: + exports += options.exports + + # Ask the user for some exports + if not exports: + print("Enter any directories to export... Enter to finish") + while True: + e = user_input("Add export") + if not e: + break + exports.append(e) + + realm = realm.upper() + + if exports and options.automount is None: + print("Do you wish to enable automount ability for these mounts?") + options.automount = user_input("Configure automount", default=False) + + # summary of results + print() + print("Setting up Kerberized NFS with the following settings:") + print("Hostname: " , hostname ) + print("Realm: " , realm ) + print("Server List: " , servers ) + print("Automount: ", options.automount ) + + mountlist = exports[:] + + # lets sanity check the exports list whilst we are printing it + if not exports: + print("Skipping directory exports") + else: + print("Exports List: ") + for d in exports[:]: + response = None + if d in exported: + response = 'Already exported' + elif not os.path.exists(d): + response = 'does not exist' + mountlist.remove(d) + elif not os.path.isdir(d): + response = 'Not a directory' + mountlist.remove(d) + + if response: + if not options.force: + response += ", Ignored" + exports.remove(d) + print(" - %s (%s)" % ( d, response )) + else: + print(" - %s" % d) + + + # Ask if this seems okay + print() + if not user_input("Continue to configure the system with these values?", default=False): + print("Abandoning.") + sys.exit(0) + + # Okay, lets do it then... + + # If they are not signed in then use a temporary ccache + ccache = None + if not krb5_valid(): + ccache_dir = tempfile.mkdtemp(prefix='krbcc') + ccache = os.path.join(ccache_dir, 'ccache') + + if not krb5_valid(ccache=ccache) and not username: + print("Enter principal that has permission to add services to this realm") + username = user_input("Admin username", allow_empty=False) + + # Make sure we are signed in + if not krb5_valid(ccache=ccache): + try: + krb5_init(username, realm, force=options.force, ccache=ccache) + except: + if not options.force: + sys.exit(1) + if ccache: + os.environ['KRB5CCNAME'] = ccache + + # Check if there is an nfs service key, create if we have to + if options.force or not ipa_service_exists(hostname, service='nfs'): + try: + ipa_service_add(hostname, service='nfs', force=options.force) + except: + if not options.force: + sys.exit(1) + else: + logging.info("Service nfs/%s already exists" % hostname) + + # check if we have the nfs server key, fetch it if we dont + if options.force or not have_keytab(hostname, service='nfs', realm=realm): + logging.debug("Fetching keytab entry") + try: + fetch_keytab(hostname, service='nfs', server=servers[0], keytab=Paths.KEYTAB) + except CalledProcessError as e: + logging.debug("'%s' failed with retcode %d: %s" % (e.cmd, e.returncode, e.output)) + + else: + logging.info("Already have the keytab cached, skipping") + + # Check if the directories we wish to export are already exported + if not exports: + logging.info("No directories to export") + + # This is somewhat naieve for now, creates a new exports.d file + for d in exports: + if options.force or d not in exported: + logging.debug("Exporting %s", d) + fp = open(Paths.EXPORTSFILE, 'a') + fp.write( "%s *(rw,sec=sys:krb5:krb5i:krb5p)\n" % ( d ) ) + fp.close() + else: + logging.debug("Path %s is already exported, skipping", d) + + if options.force or exports: + try: + update_exports() + except CalledProcessError as e: + logging.error("'%s' failed with retcode %d: %s" % (e.cmd, e.returncode, e.output)) + + if options.automount: + logging.debug("Configuring automount") + for d in mountlist: + try: + mapadd( hostname, d ) + except: + logging.error("Adding automount map for %s failed" % d) + + + # Restart any services + try: + service_restart('nfs-server', force=options.force) + except: + pass + + + # Clean up any temporary stuff we made + try: + if ccache: + os.unlink(ccache) + except: + pass + + try: + if ccache_dir: + os.rmdir(ccache_dir) + except: + pass + + print("Finished.") + + + +# Setup the logger, default to only error messages +logging.basicConfig(level=logging.INFO, format='%(message)s') + +# use this to suppress error messages from subprocesses +devnull = open(os.devnull, 'w') + +# boilerplate to launch main and handle the fallout +try: + if __name__ == "__main__": + sys.exit(main()) +except SystemExit as e: + sys.exit(e) +except KeyboardInterrupt: + sys.exit(1) +except RuntimeError as e: + sys.exit(e) diff --git a/freeipa.spec.in b/freeipa.spec.in index fbe7ff9..271d9d2 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -1269,6 +1269,7 @@ fi %license COPYING %{_sbindir}/ipa-client-install %{_sbindir}/ipa-client-automount +%{_sbindir}/ipa-client-nfsexport %{_sbindir}/ipa-certupdate %{_sbindir}/ipa-getkeytab %{_sbindir}/ipa-rmkeytab From 5ca055d2b7ced3bc06a4077ff0b4c08ca10761ef Mon Sep 17 00:00:00 2001 From: Justin Mitchell <jumit...@redhat.com> Date: Thu, 10 Nov 2016 16:07:58 +0000 Subject: [PATCH 2/3] Clean up the script to pass pylint cleanly --- client/ipa-client-nfsexport | 162 +++++++++++++++++++++----------------------- 1 file changed, 78 insertions(+), 84 deletions(-) diff --git a/client/ipa-client-nfsexport b/client/ipa-client-nfsexport index ef47942..9a0e244 100755 --- a/client/ipa-client-nfsexport +++ b/client/ipa-client-nfsexport @@ -31,9 +31,7 @@ from __future__ import print_function try: import sys import os - import time import tempfile - import dns import socket import netaddr import logging @@ -57,7 +55,7 @@ error was: sys.exit(1) -class Paths: +class Paths(object): """Collection of pathnames and executables to use""" IPA_CLI = "/usr/bin/ipa" IPA_GETKEYTAB = "/usr/sbin/ipa-getkeytab" @@ -84,12 +82,12 @@ def parse_options(): parser.add_argument("--realm", dest="realm", help="realm name") parser.add_argument("--hostname", dest="hostname", help="The hostname of this machine (FQDN)") parser.add_argument("--username", dest="username", help="Kerberos Username") - parser.add_argument("--force", action="store_true", + parser.add_argument("--force", action="store_true", help="Perform actions even if unneccessary") parser.add_argument("-v", "--verbose", help="Increase Verbosity", action="count") - parser.add_argument("--automount", dest="automount", default=None, action="store_true", + parser.add_argument("--automount", dest="automount", default=None, action="store_true", help="Configure mounts for automount use") - parser.add_argument("--noautomount", dest="automount", default=None, action="store_false", + parser.add_argument("--noautomount", dest="automount", default=None, action="store_false", help="Do not configure mounts for automount use") options = parser.parse_args() @@ -108,7 +106,7 @@ def have_keytab( hostname, service='host', realm=None ): if realm: principal = '%s/%s@%s' % (service, hostname, realm.upper()) - logging.debug("Checking for principal %s in keytab" % principal ) + logging.debug("Checking for principal %s in keytab", principal ) try: out = subprocess.check_output([ Paths.KLIST, '-k'], stderr=devnull) @@ -117,7 +115,7 @@ def have_keytab( hostname, service='host', realm=None ): return True except CalledProcessError as e: - logging.debug("klist Error ret=%d %s" , e.returncode, e) + logging.debug("klist Error ret=%d %s", e.returncode, e) return False return False @@ -144,7 +142,7 @@ def get_resolver_domains(): domain = line.split()[-1] elif line.lower().startswith('search'): domains += line.split()[1:] - except: + except StandardError: pass logging.debug(" resolv.conf search domains: " + str(domains)) @@ -153,13 +151,13 @@ def get_resolver_domains(): domains.append(domain) return domains - + def dns_search_srv(domain, srv_record_name, default_port): """Search for SRV records in the domain""" qname = '%s.%s' % (srv_record_name, domain) try: answers = resolver.query(qname, rdatatype.SRV) - except DNSException, e: + except DNSException: answers = [] logging.debug(" SRV query for " + qname ) @@ -169,7 +167,7 @@ def dns_search_srv(domain, srv_record_name, default_port): logging.debug(" - target=" + str(answer.target) + " port=" + str(answer.port)) host = str(answer.target).rstrip(".") if not host: - continue; + continue if default_port is not None and answer.port != default_port: host = "%s:%s" % (host, str(answer.port)) @@ -179,7 +177,7 @@ def dns_search_srv(domain, srv_record_name, default_port): return servers - + def search_servers( domain ): """Build a list of possible domain names, then search it for LDAP servers """ @@ -221,7 +219,6 @@ def search_realm(hostname): logging.debug("Searching DNS for Kerberos Realm...") domain = None - realm = None if hostname and not valid_ip(hostname): p = hostname.find(".") if p != -1: @@ -237,14 +234,14 @@ def search_realm(hostname): logging.debug(" TXT query for %s" , qname) try: answers = resolver.query(qname, rdatatype.TXT) - except DNSException, e: + except DNSException: raise for answer in answers: logging.debug(" - Answer: %s" , answer.strings) try: return answer.strings[0] - except: + except LookupError: pass raise RuntimeError("Not found") @@ -305,7 +302,7 @@ def search_exports(): if not domain in exports: exports.append(domain) return exports - except: + except StandardError: raise def krb5_fetchinfo(): @@ -314,9 +311,9 @@ def krb5_fetchinfo(): cmd = [ Paths.KLIST ] ret = check_output(cmd) matches = re.findall(r'Default principal: (.*)@(.*)\n', ret) - logging.debug("results: %s" % matches) + logging.debug("results: %s" , matches) return dict( username=matches[0][0], realm=matches[0][1] ) - except: + except StandardError: logging.debug("klist extract failed: %s", sys.exc_info()[1]) @@ -325,7 +322,7 @@ def krb5_valid(ccache=None): try: cmd = [ Paths.KLIST, '-s' ] if ccache: - cmd += [ '-c', ccache ]; + cmd += [ '-c', ccache ] ret = check_call(cmd) if ret > 0: logging.debug("No current valid keys found in ccache") @@ -333,7 +330,7 @@ def krb5_valid(ccache=None): else: logging.debug("Found valid ccache") return True - except: + except CalledProcessError: logging.debug("klist error: %s", sys.exc_info()) return False @@ -355,33 +352,33 @@ def krb5_init( username, realm=None, force=False, ccache=None): ret = check_call(cmd, stdin=sys.stdin, stdout=sys.stdout) if ret != 0: logging.error("kinit returned %d", ret) - except CalledProcessError as e: + except CalledProcessError: raise def ipa_service_exists( hostname, service='nfs', realm=None): """Ask IPA if this service principal exists""" principal = '%s/%s' % ( service, hostname ) - try: - out = check_output([ Paths.IPA_CLI, 'service-show', principal ], stderr=devnull) + try: + check_output([ Paths.IPA_CLI, 'service-show', principal ], stderr=devnull) return True - except CalledProcessError as e: - logging.debug("Failed to find service %s" % principal) + except CalledProcessError: + logging.debug("Failed to find service %s", principal) return False def ipa_service_add( hostname, service='nfs', realm=None, force=False): """Create this service principal via IPA""" principal = '%s/%s' % ( service, hostname ) - logging.debug("Adding service %s" % principal) + logging.debug("Adding service %s", principal) cmd = [ Paths.IPA_CLI, 'service-add', principal ] if force: cmd.append('--force') - try: - out = check_output( cmd ) + try: + check_output( cmd ) except CalledProcessError as e: - logging.error("'%s' failed with retcode %d: %s" % (cmd[0], e.returncode, e.output)) + logging.error("'%s' failed with retcode %d: %s", cmd[0], e.returncode, e.output) raise def fetch_keytab(hostname, service='nfs', server=None, keytab=None): @@ -393,9 +390,9 @@ def fetch_keytab(hostname, service='nfs', server=None, keytab=None): if keytab: cmd += [ '-k', keytab ] - try: - out = check_output(cmd) - except: + try: + check_output(cmd) + except CalledProcessError: raise @@ -410,7 +407,7 @@ def service_restart(service, force=False): ret = check_call(cmd, stdout=devnull, stderr=devnull) if ret == 0: enabled = True - except CalledProcessError as e: + except CalledProcessError: pass # Enable it @@ -418,20 +415,20 @@ def service_restart(service, force=False): cmd = [ Paths.SYSTEMCTL, 'enable', service ] try: ret = check_call(cmd, stdout=devnull, stderr=devnull) - except CalledProcessError as e: - logging.error("Error enabling service %s" % service) + except CalledProcessError: + logging.error("Error enabling service %s", service) raise - + # run/restart it if force: cmd = [ Paths.SYSTEMCTL, 'restart', service ] else: cmd = [ Paths.SYSTEMCTL, 'try-restart', service ] - + try: ret = check_call(cmd, stdout=devnull, stderr=devnull) - except CalledProcessError as e: - logging.error("Error restarting service %s" % service) + except CalledProcessError: + logging.error("Error restarting service %s", service) raise @@ -442,7 +439,7 @@ def update_exports(): try: ret = check_output( cmd ) logging.debug(ret) - except: + except CalledProcessError: raise def load_ipa_config(pathname): @@ -452,22 +449,22 @@ def load_ipa_config(pathname): values = dict() try: config.read([pathname]) - except: + except ConfigParser.Error: raise try: values['realm'] = config.get('global', 'realm') - except: + except ConfigParser.Error: pass try: values['server'] = config.get('global', 'server') - except: + except ConfigParser.Error: pass try: values['hostname'] = config.get('global', 'host') - except: + except ConfigParser.Error: pass if not values: @@ -483,57 +480,55 @@ def mapadd( hostname, directory ): else: mapname = 'auto.%s' % directory - logging.debug("Adding map '%s' %s:%s" % (mapname, hostname, directory)) + logging.debug("Adding map '%s' %s:%s" , mapname, hostname, directory) try: # Create the map - logging.debug("Create map '%s'" % mapname) + logging.debug("Create map '%s'", mapname) cmd = [ Paths.IPA_CLI, 'automountmap-add', 'default', mapname ] - ret = check_output(cmd, stderr=devnull) + check_output(cmd, stderr=devnull) # set the directory and make a sub of auto.master - logging.debug("Add map directory '%s'" % directory) + logging.debug("Add map directory '%s'", directory) cmd = [ Paths.IPA_CLI, 'automountkey-add', 'default', '--key', directory, '--info', mapname, 'auto.master' ] - ret = check_output(cmd, stderr=devnull) + check_output(cmd, stderr=devnull) # Now set the mapping - logging.debug("Set map key '%s'" % hostname) + logging.debug("Set map key '%s'", hostname) cmd = [ Paths.IPA_CLI, 'automountkey-add', 'default', '--key', '*', '--info', "-fstype=nfs4,rw,sec=krb5p,soft,rsize=8192,wsize=8192 %s:%s/&" % (hostname, directory), mapname ] - ret = check_output(cmd, stderr=devnull) + check_output(cmd, stderr=devnull) except CalledProcessError as e: - logging.debug("Error creating map %s: %s " % (mapname, e) ) + logging.debug("Error creating map %s: %s ", mapname, e) raise def main(): - env={"PATH":"/bin:/sbin:/usr/kerberos/bin:/usr/kerberos/sbin:/usr/bin:/usr/sbin"} - tool = 'yum' if os.path.exists( Paths.DNF ): tool = 'dnf' # basic sanity checks first if not os.path.exists( Paths.IPA_CLI ): - logging.error("%s not found. Try '%s install ipa-admintools' first" % (Paths.IPA_CLI, tool)) + logging.error("%s not found. Try '%s install ipa-admintools' first", Paths.IPA_CLI, tool) sys.exit(1) if not os.path.exists( Paths.IPA_GETKEYTAB ): - logging.error("%s not found. Try '%s install ipa-client' first" % (Paths.IPA_GETKEYTAB, tool)) + logging.error("%s not found. Try '%s install ipa-client' first", Paths.IPA_GETKEYTAB, tool) sys.exit(1) if not os.path.exists( Paths.KLIST ): - logging.error("%s not found. Try '%s install krb5-workstation' first" % (Paths.KLIST, tool)) + logging.error("%s not found. Try '%s install krb5-workstation' first", Paths.KLIST, tool) sys.exit(1) if not os.path.exists( Paths.KINIT ): - logging.error("%s not found. Try '%s install krb5-workstation' first" % ( Paths.KINIT, tool)) + logging.error("%s not found. Try '%s install krb5-workstation' first", Paths.KINIT, tool) sys.exit(1) if not os.path.exists( Paths.EXPORTFS ): - logging.error("%s not found. Try '%s install nfs-utils' first" % ( Paths.EXPORTFS, tool)) + logging.error("%s not found. Try '%s install nfs-utils' first", Paths.EXPORTFS, tool) sys.exit(1) @@ -555,28 +550,27 @@ def main(): ipaconf = dict() try: ipaconf = load_ipa_config(Paths.IPACONFIG) - except: - logging.debug("load config failed: %s" % sys.exc_value); - pass + except StandardError: + logging.debug("load config failed: %s", sys.exc_value) # If we have them, try using the ipa config values next if not hostname: - try: + try: hostname = ipaconf['hostname'] - except: + except LookupError: pass if not realm: try: realm = ipaconf['realm'] - except: + except LookupError: pass if not servers: try: newserver = ipaconf['server'] servers.append( newserver ) - except: + except LookupError: pass @@ -599,7 +593,7 @@ def main(): if not realm: try: realm = search_realm(hostname) - except: + except StandardError: pass # Maybe we have signed in already and that can tell us? @@ -610,14 +604,14 @@ def main(): realm = kinfo['realm'] if not username: username = kinfo['username'] - except: + except StandardError: pass # We cant find a realm so ask the user if not realm: domain = None if hostname and not valid_ip(hostname): - p = hostname.find(".") + p = str(hostname).find(".") if p != -1: domain = hostname[p+1:] print("Unable to determine realm, and not provided by --realm") @@ -637,7 +631,7 @@ def main(): # Grab a list of what is already exported on this system try: exported = search_exports() - except: + except StandardError: pass # Has the user given a manual list of directories to export @@ -653,7 +647,7 @@ def main(): break exports.append(e) - realm = realm.upper() + realm = str(realm).upper() if exports and options.automount is None: print("Do you wish to enable automount ability for these mounts?") @@ -716,7 +710,7 @@ def main(): if not krb5_valid(ccache=ccache): try: krb5_init(username, realm, force=options.force, ccache=ccache) - except: + except CalledProcessError: if not options.force: sys.exit(1) if ccache: @@ -726,11 +720,11 @@ def main(): if options.force or not ipa_service_exists(hostname, service='nfs'): try: ipa_service_add(hostname, service='nfs', force=options.force) - except: + except CalledProcessError: if not options.force: sys.exit(1) else: - logging.info("Service nfs/%s already exists" % hostname) + logging.info("Service nfs/%s already exists", hostname) # check if we have the nfs server key, fetch it if we dont if options.force or not have_keytab(hostname, service='nfs', realm=realm): @@ -738,7 +732,7 @@ def main(): try: fetch_keytab(hostname, service='nfs', server=servers[0], keytab=Paths.KEYTAB) except CalledProcessError as e: - logging.debug("'%s' failed with retcode %d: %s" % (e.cmd, e.returncode, e.output)) + logging.debug("'%s' failed with retcode %d: %s", e.cmd, e.returncode, e.output) else: logging.info("Already have the keytab cached, skipping") @@ -756,26 +750,26 @@ def main(): fp.close() else: logging.debug("Path %s is already exported, skipping", d) - + if options.force or exports: try: update_exports() except CalledProcessError as e: - logging.error("'%s' failed with retcode %d: %s" % (e.cmd, e.returncode, e.output)) + logging.error("'%s' failed with retcode %d: %s", e.cmd, e.returncode, e.output) if options.automount: logging.debug("Configuring automount") for d in mountlist: try: mapadd( hostname, d ) - except: - logging.error("Adding automount map for %s failed" % d) + except CalledProcessError: + logging.error("Adding automount map for %s failed", d) # Restart any services try: service_restart('nfs-server', force=options.force) - except: + except CalledProcessError: pass @@ -783,13 +777,13 @@ def main(): try: if ccache: os.unlink(ccache) - except: + except OSError: pass try: if ccache_dir: os.rmdir(ccache_dir) - except: + except OSError: pass print("Finished.") @@ -799,7 +793,7 @@ def main(): # Setup the logger, default to only error messages logging.basicConfig(level=logging.INFO, format='%(message)s') -# use this to suppress error messages from subprocesses +# use this to suppress error messages from subprocesses devnull = open(os.devnull, 'w') # boilerplate to launch main and handle the fallout From f68cc2454e191a4e39573dcb00fde2c2c254b62f Mon Sep 17 00:00:00 2001 From: Justin Mitchell <jumit...@redhat.com> Date: Mon, 14 Nov 2016 10:20:32 +0000 Subject: [PATCH 3/3] Clean up lint items for f24+ --- client/ipa-client-nfsexport | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/client/ipa-client-nfsexport b/client/ipa-client-nfsexport index 9a0e244..89515af 100755 --- a/client/ipa-client-nfsexport +++ b/client/ipa-client-nfsexport @@ -39,7 +39,9 @@ try: import tempfile import ConfigParser import re + import six + from six.moves import input from dns import resolver, rdatatype from dns.exception import DNSException from argparse import ArgumentParser @@ -142,7 +144,7 @@ def get_resolver_domains(): domain = line.split()[-1] elif line.lower().startswith('search'): domains += line.split()[1:] - except StandardError: + except IOError: pass logging.debug(" resolv.conf search domains: " + str(domains)) @@ -246,12 +248,11 @@ def search_realm(hostname): raise RuntimeError("Not found") - def user_input(prompt, default=None, allow_empty=True): """Prompt the user for some input, with optional default value""" - if isinstance(default, basestring): - ret = raw_input("%s [%s]: " % (prompt, default)) + if isinstance(default, six.string_types): + ret = input("%s [%s]: " % (prompt, default)) if not ret.strip(): return default else: @@ -262,7 +263,7 @@ def user_input(prompt, default=None, allow_empty=True): else: choice = "no" while True: - ret = raw_input("%s [%s]: " % (prompt, choice)) + ret = input("%s [%s]: " % (prompt, choice)) if not ret.strip(): return default elif ret.lower()[0] == "y": @@ -272,7 +273,7 @@ def user_input(prompt, default=None, allow_empty=True): elif isinstance(default, int): while True: try: - ret = raw_input("%s [%s]: " % (prompt, choice)) + ret = input("%s [%s]: " % (prompt, choice)) if not ret.strip(): return default ret = int(ret) @@ -282,7 +283,7 @@ def user_input(prompt, default=None, allow_empty=True): return ret else: while True: - ret = raw_input("%s: " % prompt) + ret = input("%s: " % prompt) if allow_empty or (ret and ret.strip()): return ret @@ -302,7 +303,7 @@ def search_exports(): if not domain in exports: exports.append(domain) return exports - except StandardError: + except IOError: raise def krb5_fetchinfo(): @@ -313,7 +314,7 @@ def krb5_fetchinfo(): matches = re.findall(r'Default principal: (.*)@(.*)\n', ret) logging.debug("results: %s" , matches) return dict( username=matches[0][0], realm=matches[0][1] ) - except StandardError: + except CalledProcessError: logging.debug("klist extract failed: %s", sys.exc_info()[1]) @@ -472,8 +473,8 @@ def load_ipa_config(pathname): return values - def mapadd( hostname, directory ): + """Add automount map to the server""" parts = directory.rsplit('/', 1) if parts[1]: mapname = 'auto.%s' % parts[1] @@ -490,12 +491,15 @@ def mapadd( hostname, directory ): # set the directory and make a sub of auto.master logging.debug("Add map directory '%s'", directory) - cmd = [ Paths.IPA_CLI, 'automountkey-add', 'default', '--key', directory, '--info', mapname, 'auto.master' ] + cmd = [ Paths.IPA_CLI, 'automountkey-add', 'default', '--key', + directory, '--info', mapname, 'auto.master' ] check_output(cmd, stderr=devnull) # Now set the mapping logging.debug("Set map key '%s'", hostname) - cmd = [ Paths.IPA_CLI, 'automountkey-add', 'default', '--key', '*', '--info', "-fstype=nfs4,rw,sec=krb5p,soft,rsize=8192,wsize=8192 %s:%s/&" % (hostname, directory), mapname ] + cmd = [ Paths.IPA_CLI, 'automountkey-add', 'default', '--key', '*', + '--info', "-fstype=nfs4,rw,sec=krb5p,soft,rsize=8192,wsize=8192 %s:%s/&" % + (hostname, directory), mapname ] check_output(cmd, stderr=devnull) except CalledProcessError as e: @@ -550,8 +554,8 @@ def main(): ipaconf = dict() try: ipaconf = load_ipa_config(Paths.IPACONFIG) - except StandardError: - logging.debug("load config failed: %s", sys.exc_value) + except (ConfigParser.Error, EOFError): + pass # If we have them, try using the ipa config values next if not hostname: @@ -593,7 +597,7 @@ def main(): if not realm: try: realm = search_realm(hostname) - except StandardError: + except (ValueError, DNSException, LookupError, RuntimeError): pass # Maybe we have signed in already and that can tell us? @@ -604,7 +608,7 @@ def main(): realm = kinfo['realm'] if not username: username = kinfo['username'] - except StandardError: + except CalledProcessError: pass # We cant find a realm so ask the user @@ -631,7 +635,7 @@ def main(): # Grab a list of what is already exported on this system try: exported = search_exports() - except StandardError: + except IOError: pass # Has the user given a manual list of directories to export
-- Manage your subscription for the Freeipa-devel mailing list: https://www.redhat.com/mailman/listinfo/freeipa-devel Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code