From 0805e18d1e4077605b382acb9322996a90782c4d Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 5 Dec 2007 18:59:24 -0500 Subject: Some initial work on kerberos authentication via a helper program, some work on making the API have access to the log files and logging everything done there. The logging work, as well as kerb testing, are incomplete at this point, though authn_configfile works fine. --- MANIFEST.in | 1 + cobbler.spec | 1 + cobbler/api.py | 56 ++++++++++++++++++++++++- cobbler/modules/authn_configfile.py | 5 +-- cobbler/modules/authn_kerberos.py | 81 +++++++++++++++++++++++++++++++++++++ cobbler/modules/authz_allowall.py | 2 +- cobbler/remote.py | 27 +++++-------- cobbler/settings.py | 1 + config/cobblerd_rotate | 10 +++-- config/settings | 1 + scripts/cobbler_auth_help | 54 +++++++++++++++++++++++++ setup.py | 2 +- 12 files changed, 214 insertions(+), 27 deletions(-) create mode 100644 cobbler/modules/authn_kerberos.py create mode 100644 scripts/cobbler_auth_help diff --git a/MANIFEST.in b/MANIFEST.in index 58a1df9..357cdcc 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -24,6 +24,7 @@ include scripts/nopxe.cgi include scripts/webui.cgi include scripts/gateway.py include scripts/post_install_trigger.cgi +include scripts/cobbler_auth_help include snippets/* recursive-include po *.pot recursive-include po *.po diff --git a/cobbler.spec b/cobbler.spec index 77a246f..afb3db6 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -114,6 +114,7 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %dir /tftpboot/images %{_bindir}/cobbler %{_bindir}/cobblerd +%{_bindir}/cobbler_auth_help %dir /etc/cobbler %config(noreplace) /etc/cobbler/default.ks %config(noreplace) /etc/cobbler/kickstart_fc5.ks diff --git a/cobbler/api.py b/cobbler/api.py index e6ef88b..6343035 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -24,6 +24,11 @@ import action_status import action_validate import sub_process import module_loader +import logging + +ERROR = 100 +INFO = 10 +DEBUG = 5 class BootAPI: @@ -37,10 +42,33 @@ class BootAPI: self.__dict__ = self.__shared_state if not BootAPI.has_loaded: + + + logger = logging.getLogger("cobbler.api") + logger.setLevel(logging.DEBUG) + ch = logging.FileHandler("/var/log/cobbler/cobbler.log") + ch.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + ch.setFormatter(formatter) + logger.addHandler(ch) + BootAPI.has_loaded = True module_loader.load_modules() self._config = config.Config(self) self.deserialize() + self.logger = logger + self.logger.debug("API handle initialized") + + self.authn = self.get_module_from_file( + "authentication", + "module", + "authn_configfile" + ) + self.authz = self.get_module_from_file( + "authorization", + "module", + "authz_allowall" + ) def version(self): """ @@ -48,6 +76,7 @@ class BootAPI: Currently checks the RPM DB, which is not perfect. Will return "?" if not installed. """ + self.logger.debug("cobbler version") cmd = sub_process.Popen("/bin/rpm -q cobbler", stdout=sub_process.PIPE, shell=True) result = cmd.communicate()[0].replace("cobbler-","") if result.find("not installed") != -1: @@ -55,7 +84,6 @@ class BootAPI: tokens = result[:result.rfind("-")].split(".") return int(tokens[0]) + 0.1 * int(tokens[1]) + 0.001 * int(tokens[2]) - def clear(self): """ Forget about current list of profiles, distros, and systems @@ -99,12 +127,14 @@ class BootAPI: """ Return a blank, unconfigured system, unattached to a collection """ + self.logger.debug("new_system") return self._config.new_system(is_subobject=is_subobject) def new_distro(self,is_subobject=False): """ Create a blank, unconfigured distro, unattached to a collection. """ + self.logger.debug("new_distro") return self._config.new_distro(is_subobject=is_subobject) @@ -112,12 +142,14 @@ class BootAPI: """ Create a blank, unconfigured profile, unattached to a collection """ + self.logger.debug("new_profile") return self._config.new_profile(is_subobject=is_subobject) def new_repo(self,is_subobject=False): """ Create a blank, unconfigured repo, unattached to a collection """ + self.logger.debug("new_repo") return self._config.new_repo(is_subobject=is_subobject) def auto_add_repos(self): @@ -125,6 +157,7 @@ class BootAPI: Import any repos this server knows about and mirror them. Credit: Seth Vidal. """ + self.logger.debug("auto_add_repos") try: import yum except: @@ -164,6 +197,7 @@ class BootAPI: for human admins, who may, for instance, forget to properly set up their TFTP servers for PXE, etc. """ + self.logger.debug("check") check = action_check.BootCheck(self._config) return check.run() @@ -176,6 +210,7 @@ class BootAPI: is not available on all platforms and can not detect "future" kickstart format correctness. """ + self.logger.debug("validateks") validator = action_validate.Validate(self._config) return validator.run() @@ -186,6 +221,7 @@ class BootAPI: /tftpboot. Any operations done in the API that have not been saved with serialize() will NOT be synchronized with this command. """ + self.logger.debug("sync") sync = action_sync.BootSync(self._config) return sync.run() @@ -194,10 +230,12 @@ class BootAPI: Take the contents of /var/lib/cobbler/repos and update them -- or create the initial copy if no contents exist yet. """ + self.logger.debug("reposync") reposync = action_reposync.RepoSync(self._config) return reposync.run(name) def status(self,mode): + self.logger.debug("status") statusifier = action_status.BootStatusReport(self._config, mode) return statusifier.run() @@ -252,4 +290,20 @@ class BootAPI: """ return module_loader.get_modules_in_category(category) + def authenticate(self,user,password): + """ + (Remote) access control. + """ + self.logger.debug("authorize(%s)" % (user)) + rc = self.authn.authenticate(self,user,password) + self.logger.debug("authorize(%s)=%s" % (user,rc)) + return rc + + def authorize(self,user,resource,arg1=None,arg2=None): + """ + (Remote) access control. + """ + rc = self.authz.authorize(self,user,resource,arg1,arg2) + self.logger.debug("authorize(%s,%s)=%s" % (user,resource,rc)) + return rc diff --git a/cobbler/modules/authn_configfile.py b/cobbler/modules/authn_configfile.py index 5740efa..30637b7 100644 --- a/cobbler/modules/authn_configfile.py +++ b/cobbler/modules/authn_configfile.py @@ -52,7 +52,7 @@ def __parse_storage(): pass return results -def authenticate(username,password): +def authenticate(api_handle,username,password): """ Validate a username/password combo, returning True/False @@ -70,7 +70,4 @@ def authenticate(username,password): return False -if __name__ == "__main__": - print authenticate("cobbler","cobbler") - print authenticate("cobbler","bogus") diff --git a/cobbler/modules/authn_kerberos.py b/cobbler/modules/authn_kerberos.py new file mode 100644 index 0000000..7f85db6 --- /dev/null +++ b/cobbler/modules/authn_kerberos.py @@ -0,0 +1,81 @@ +""" +Authentication module that uses kerberos. + +Copyright 2007, Red Hat, Inc +Michael DeHaan + +This software may be freely redistributed under the terms of the GNU +general public license. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +# NOTE: this is not using 'straight up' kerberos in that we +# relay passwords through cobblerd for authentication, that may +# be done later. It does of course check against kerberos, +# however. + +# ALSO NOTE: we're calling out to a Perl program to make +# this work. You must install Authen::Simple::Kerberos +# from CPAN and the Kerberos libraries for this to work. +# See the Cobbler Wiki for more info. + +# ALSO ALSO NOTE: set kerberos_realm in /var/lib/cobbler/settings +# to something appropriate or this will never work. CASING +# MATTERS. example.com != EXAMPLE.COM. + +import distutils.sysconfig +import ConfigParser +import sys +import os +from rhpl.translate import _, N_, textdomain, utf8 +import md5 +import traceback +# since sub_process isn't available on older OS's +try: + import sub_process as subprocess +except: + import subprocess + +plib = distutils.sysconfig.get_python_lib() +mod_path="%s/cobbler" % plib +sys.path.insert(0, mod_path) + +import cexceptions +import utils + +def register(): + """ + The mandatory cobbler module registration hook. + """ + return "authn" + +def authenticate(api_handle,username,password): + """ + Validate a username/password combo, returning True/False + Uses cobbler_auth_helper + """ + + realm = self.api.settings().kerberos_realm + api_handle.logger.debug("authenticating %s against %s" % (username,realm)) + + rc = subprocess.call([ + "/usr/bin/cobbler_auth_help", + "--method=kerberos", + "--username=%s" % username, + "--password=%s" % password, + "--realm=%s" % realm + ]) + print rc + if rc == 42: + api_handle.logger.debug("authenticated ok") + # authentication ok (FIXME: log) + return True + else: + api_handle.logger.debug("authentication failed") + # authentication failed + return False + + diff --git a/cobbler/modules/authz_allowall.py b/cobbler/modules/authz_allowall.py index 4125ed6..1b05630 100644 --- a/cobbler/modules/authz_allowall.py +++ b/cobbler/modules/authz_allowall.py @@ -32,7 +32,7 @@ def register(): """ return "authz" -def authorize(user,resource,arg1=None,arg2=None): +def authorize(api_handle,user,resource,arg1=None,arg2=None): """ Validate a user against a resource. """ diff --git a/cobbler/remote.py b/cobbler/remote.py index 081f8c8..e84827a 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -20,7 +20,6 @@ import os import SimpleXMLRPCServer from rhpl.translate import _, N_, textdomain, utf8 import xmlrpclib -import logging import random import base64 @@ -52,7 +51,7 @@ class CobblerXMLRPCInterface: def __init__(self,api,logger): self.api = api - self.logger = logger + self.logger = self.api.logger def __sorter(self,a,b): return cmp(a["name"],b["name"]) @@ -365,17 +364,6 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): self.token_cache = {} self.object_cache = {} random.seed(time.time()) - self.authn = self.api.get_module_from_file( - "authentication", - "module", - "authn_configfile" - ) - self.authz = self.api.get_module_from_file( - "authorization", - "module", - "authz_allowall" - ) - def __next_id(self,retry=0): """ @@ -435,7 +423,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): FIXME: currently looks for users in /etc/cobbler/auth.conf Would be very nice to allow for PAM and/or just Kerberos. """ - return self.authn.authenticate(input_user,input_password) + return self.api.authenticate(input_user,input_password) def __validate_token(self,token): """ @@ -457,6 +445,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): raise CX(_("invalid token: %s" % token)) def check_access(self,token,resource,arg1=None,arg2=None): + self.logger.debug("check_access(%s, %s)" % (token,resource)) validated = self.__validate_token(token) return self.__authorize(token,resource,arg1,arg2) @@ -473,17 +462,18 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): method calls. The token will time out after a set interval if not used. Re-logging in permitted. """ + self.logger.debug("login (%s)" % login_user) if self.__validate_user(login_user,login_password): token = self.__make_token(login_user) - self.logger.info("login succeeded: %s" % login_user) + self.logger.debug("login succeeded: %s" % login_user) return token else: - self.logger.info("login failed: %s" % login_user) + self.logger.debug("login failed: %s" % login_user) raise CX(_("login failed: %s") % login_user) def __authorize(self,token,resource,arg1=None,arg2=None): user = self.__get_user_from_token(token) - if self.authz.authorize(user,resource,arg1,arg2): + if self.api.authorize(user,resource,arg1,arg2): return True else: raise CX(_("user does not have access to resource: %s") % resource) @@ -492,6 +482,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): """ Retires a token ahead of the timeout. """ + self.logger.debug("logout(%s)" % token) if self.token_cache.has_key(token): del self.token_cache[token] return True @@ -501,6 +492,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): """ This is a demo function that does not return anything useful. """ + self.logger.debug("token_check(%s)" % token) self.__validate_token(token) return True @@ -830,3 +822,4 @@ class CobblerReadWriteXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer): self.allow_reuse_address = True SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self,args) + diff --git a/cobbler/settings.py b/cobbler/settings.py index 8f9527d..c1fe232 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -34,6 +34,7 @@ DEFAULTS = { "dnsmasq_bin" : "/usr/sbin/dnsmasq", "dnsmasq_conf" : "/etc/dnsmasq.conf", "httpd_bin" : "/usr/sbin/httpd", + "kerberos_realm" : "example.org", "kernel_options" : { "lang" : " ", "text" : None, diff --git a/config/cobblerd_rotate b/config/cobblerd_rotate index 94fac8d..0e4bcbf 100644 --- a/config/cobblerd_rotate +++ b/config/cobblerd_rotate @@ -1,4 +1,4 @@ -/var/log/cobbler/cobblerd.log { +/var/log/cobbler/cobbler.log { missingok notifempty rotate 4 @@ -10,10 +10,14 @@ endscript } -/var/log/httpd/cobbler_wui.log { +/var/log/cobbler/webui.log { missingok notifempty rotate 4 weekly + postrotate + if [ -f /var/lock/subsys/cobblerd ]; then + /etc/init.d/cobblerd condrestart + fi + endscript } - diff --git a/config/settings b/config/settings index d47635a..f05c288 100644 --- a/config/settings +++ b/config/settings @@ -10,6 +10,7 @@ dhcpd_conf: /etc/dhcpd.conf dnsmasq_bin: /usr/sbin/dnsmasq dnsmasq_conf: /etc/dnsmasq.conf httpd_bin: /usr/sbin/httpd +kerberos_realm: 'example.org' kernel_options: ksdevice: eth0 lang: ' ' diff --git a/scripts/cobbler_auth_help b/scripts/cobbler_auth_help new file mode 100644 index 0000000..12a38b0 --- /dev/null +++ b/scripts/cobbler_auth_help @@ -0,0 +1,54 @@ +#!/usr/bin/perl + +# Kerberos helper for logins +# +# Copyright 2007, Red Hat, Inc +# Michael DeHaan +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +# Usage: +# cobbler_auth_helper kerberos username pass +# (may do other auth types later) +# Returns: +# 0 on ok, non-0 on failure +# API info: +# http://search.cpan.org/~chansen/Authen-Simple-Kerberos-0.1/ + +use warnings; +use strict; +use Authen::Simple::Kerberos; +use Getopt::Long; + +my $method; +my $username; +my $realm; +my $password; +my $verbose=0; + +my $result = GetOptions( + "method=s" => \$method, + "username=s" => \$username, + "realm=s" => \$realm, + "password=s" => \$password, +); + +my $kerberos = Authen::Simple::Kerberos->new( + realm => $realm +); + +print "authenticating: $username against $method $realm ($password)\n" if $verbose; + +if ( $kerberos->authenticate( $username, $password ) ) { + print "ok\n" if $verbose; + exit(42); +} + +print "denied\n" if $verbose; +exit(1); + diff --git a/setup.py b/setup.py index e839fd6..75b06f0 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ if __name__ == "__main__": "cobbler/modules", "cobbler/webui", ], - scripts = ["scripts/cobbler", "scripts/cobblerd"], + scripts = ["scripts/cobbler", "scripts/cobblerd", "scripts/cobbler_auth_help"], data_files = [ (modpython, ['scripts/index.py']), # cgi files -- cgit