From f2406b0115acd0c2a34ac27f572037e02c54ddd8 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 28 Aug 2008 17:54:13 -0400 Subject: Lots of work on the acl module, still need to test some niche cases and ensure the authz_configfile still works, though ownership is looking pretty good at this point with ACL's tacked on after normal authz approval --- MANIFEST.in | 1 + Makefile | 2 + cobbler.spec | 1 + cobbler/api.py | 6 +- cobbler/modules/authz_allowall.py | 2 +- cobbler/modules/authz_configfile.py | 5 +- cobbler/modules/authz_ownership.py | 76 +++++++++++++++++-------- cobbler/remote.py | 6 +- config/acls.conf | 110 +++++++++++++++--------------------- config/modules.conf | 25 ++------ setup.py | 1 + 11 files changed, 120 insertions(+), 115 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index ab952513..659f04a2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ include loaders/COPYING_ELILO include loaders/elilo-3.6-ia64.efi include loaders/menu.c32 +include config/acls.conf include config/cobbler.conf include config/cobbler_svc.conf include config/rsync.exclude diff --git a/Makefile b/Makefile index 8fad1227..18652e78 100644 --- a/Makefile +++ b/Makefile @@ -39,12 +39,14 @@ devinstall: -cp /etc/cobbler/settings /tmp/cobbler_settings -cp /etc/cobbler/modules.conf /tmp/cobbler_modules.conf -cp /etc/httpd/conf.d/cobbler.conf /tmp/cobbler_http.conf + -cp /etc/cobbler/acls.conf /tmp/cobbler_acls.conf -cp /etc/cobbler/users.conf /tmp/cobbler_users.conf -cp /etc/cobbler/users.digest /tmp/cobbler_users.digest make install -cp /tmp/cobbler_settings /etc/cobbler/settings -cp /tmp/cobbler_modules.conf /etc/cobbler/modules.conf -cp /tmp/cobbler_users.conf /etc/cobbler/users.conf + -cp /tmp/cobbler_acls.conf /etc/cobbler/acls.conf -cp /tmp/cobbler_users.digest /etc/cobbler/users.digest -cp /tmp/cobbler_http.conf /etc/httpd/conf.d/cobbler.conf find /var/lib/cobbler/triggers | xargs chmod +x diff --git a/cobbler.spec b/cobbler.spec index f8588edc..faca0e11 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -130,6 +130,7 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %config(noreplace) /etc/logrotate.d/cobblerd_rotate %config(noreplace) /etc/cobbler/modules.conf %config(noreplace) /etc/cobbler/users.conf +%config(noreplace) /etc/cobbler/acls.conf %dir %{python_sitelib}/cobbler %dir %{python_sitelib}/cobbler/yaml %dir %{python_sitelib}/cobbler/modules diff --git a/cobbler/api.py b/cobbler/api.py index b78d8cf7..3defb05f 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -38,6 +38,7 @@ import sub_process import module_loader import kickgen import yumgen +import acls import logging import os @@ -89,6 +90,8 @@ class BootAPI: self._config = config.Config(self) self.deserialize() + self.acl_engine = acls.AclEngine() + self.authn = self.get_module_from_file( "authentication", "module", @@ -492,8 +495,9 @@ class BootAPI: """ (Remote) access control. """ - rc = self.authz.authorize(self,user,resource,arg1,arg2) + rc = self.authz.authorize(self,user,resource,arg1,arg2,self.acl_engine) self.log("authorize",[user,resource,arg1,arg2,rc],debug=True) + # if we clear authz, now ask the ACL engine return rc def build_iso(self,iso=None,profiles=None,systems=None,tempdir=None): diff --git a/cobbler/modules/authz_allowall.py b/cobbler/modules/authz_allowall.py index 890f144f..9367c607 100644 --- a/cobbler/modules/authz_allowall.py +++ b/cobbler/modules/authz_allowall.py @@ -40,7 +40,7 @@ def register(): """ return "authz" -def authorize(api_handle,user,resource,arg1=None,arg2=None): +def authorize(api_handle,user,resource,arg1=None,arg2=None,acl_engine=None): """ Validate a user against a resource. """ diff --git a/cobbler/modules/authz_configfile.py b/cobbler/modules/authz_configfile.py index ddb02242..748ad7c6 100644 --- a/cobbler/modules/authz_configfile.py +++ b/cobbler/modules/authz_configfile.py @@ -47,8 +47,7 @@ def __parse_config(): alldata[g][o] = 1 return alldata - -def authorize(api_handle,user,resource,arg1=None,arg2=None): +def authorize(api_handle,user,resource,arg1=None,arg2=None,acl_engine=None): """ Validate a user against a resource. All users in the file are permitted by this module. @@ -59,7 +58,7 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None): data = __parse_config() for g in data: if user in data[g]: - return 1 + return acl_engine.can_access(g,user,resource,arg1,arg2) return 0 if __name__ == "__main__": diff --git a/cobbler/modules/authz_ownership.py b/cobbler/modules/authz_ownership.py index aed9cd66..ecef5516 100644 --- a/cobbler/modules/authz_ownership.py +++ b/cobbler/modules/authz_ownership.py @@ -58,7 +58,7 @@ def __parse_config(): alldata[g][o] = 1 return alldata -def __authorize_kickstart(api_handle, user, user_groups, kickstart): +def __authorize_kickstart(api_handle, group, user, kickstart, resource, arg1, arg2, acl_engine): # the authorization rules for kickstart editing are a bit # of a special case. Non-admin users can edit a kickstart # only if all objects that depend on that kickstart are @@ -80,27 +80,29 @@ def __authorize_kickstart(api_handle, user, user_groups, kickstart): lst = api_handle.find_profile(kickstart=kickstart, return_list=True) lst.extend(api_handle.find_system(kickstart=kickstart, return_list=True)) for obj in lst: - if not __is_user_allowed(obj, user, user_groups): + if not __is_user_allowed(obj, group, user, resource, arg1, arg2, acl_engine): return 0 return 1 -def __is_user_allowed(obj, user, user_groups): +def __is_user_allowed(obj, group, user, resource, arg1, arg2, acl_engine): if obj.owners == []: # no ownership restrictions, cleared - return 1 + print "DEBUG: check Z1" + return acl_engine.can_access(group, user, resource, arg1, arg2) for allowed in obj.owners: if user == allowed: # user match - return 1 + print "DEBUG: check Z2" + return acl_engine.can_access(group, user, resource, arg1, arg2) # else look for a group match - for group in user_groups: - if group == allowed and user in user_groups[group]: - return 1 + if group == allowed: + print "DEBUG: check Z3" + return acl_engine.can_access(group, user, resource, arg1, arg2) return 0 -def authorize(api_handle,user,resource,arg1=None,arg2=None): +def authorize(api_handle,user,resource,arg1=None,arg2=None,acl_engine=None): """ Validate a user against a resource. All users in the file are permitted by this module. @@ -111,15 +113,17 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None): # everybody can get read-only access to everything # if they pass authorization, they don't have to be in users.conf if resource is not None: + # FIXME: /cobbler/web should not be subject to user check in any case for x in [ "get", "read", "/cobbler/web" ]: if resource.startswith(x): - return 1 + print "- DEBUG: get/read/other always ok" + return 1 # read operation is always ok. user_groups = __parse_config() # classify the type of operation modify_operation = False - for criteria in ["save","remove","modify","write","edit"]: + for criteria in ["save","copy","rename","remove","modify","write","edit"]: if resource.find(criteria) != -1: modify_operation = True @@ -127,14 +131,18 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None): # FIXME: deal with the problem of deleted parents and promotion found_user = False - for g in user_groups: + found_group = None + grouplist = user_groups.keys() + for g in grouplist: for x in user_groups[g]: if x == user: + found_group = g found_user = True # if user is in the admin group, always authorize # regardless of the ownership of the object. if g == "admins" or g == "admin": - return 1 + print "DEBUG: check A" + return acl_engine.can_access(found_group,user,resource,arg1,arg2) break if not found_user: @@ -144,7 +152,8 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None): if not modify_operation: # sufficient to allow access for non save/remove ops to all # users for now, may want to refine later. - return 1 + print "DEBUG: check B" + return acl_engine.can_access(found_group,user,resource,arg1,arg2) # now we have a modify_operation op, so we must check ownership # of the object. remove ops pass in arg1 as a string name, @@ -153,9 +162,11 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None): # function, rather than going through the rest of the code here. if resource.find("write_kickstart") != -1: + print "DEBUG: check C" return __authorize_kickstart(api_handle,user,user_groups,arg1) elif resource.find("read_kickstart") != -1: - return True + print "DEBUG: check D" + return acl_engine.can_access(found_group,user,resource,arg1,arg2) obj = None if resource.find("remove") != -1: @@ -172,19 +183,40 @@ def authorize(api_handle,user,resource,arg1=None,arg2=None): # if the object has no ownership data, allow access regardless if obj.owners is None or obj.owners == []: - return 1 + print "DEBUG: check E" + return acl_engine.can_access(found_group,user,resource,arg1,arg2) - return __is_user_allowed(obj,user,user_groups) + print "DEBUG: check F" + return __is_user_allowed(obj,found_group,user,resource,arg1,arg2,acl_engine) if __name__ == "__main__": # real tests are contained in tests/tests.py import api as cobbler_api + import acls + acl_engine = acls.AclEngine() api = cobbler_api.BootAPI() print __parse_config() - print authorize(api, "admin1", "sync") - d = api.find_distro("F9B-i386") - d.set_owners(["allowed"]) + print authorize(api, "testing", "sync", acl_engine=acl_engine) + d = api.find_distro("F9I-i386") + d.set_owners(["jradmin"]) api.add_distro(d) - print authorize(api, "admin1", "save_distro", d) - print authorize(api, "basement2", "save_distro", d) + p = api.find_profile("F9I-i386") + p.set_owners(["jradmin"]) + api.add_profile(p) + s = api.find_system("foo") + s.set_owners(["jradmin"]) + api.add_system(s) + print "**** TRY SOMETHING I CAN'T DO" + print authorize(api, "testing", "save_profile", p, acl_engine=acl_engine) + print "**** TRY SOMETHING I CAN'T DO" + print authorize(api, "testing", "save_distro", d, acl_engine=acl_engine) + print "***** EDIT SYSTEM I OWN" + print authorize(api, "testing", "save_system", s, acl_engine=acl_engine) + s = api.find_system("foo") + s.set_owners("notyou") + api.add_system(s) + print "***** EDIT SYSTEM I DONT OWN" + print authorize(api, "testing", "save_system", s, acl_engine=acl_engine) + + diff --git a/cobbler/remote.py b/cobbler/remote.py index cb9e51f3..8b6ea3d9 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -1138,7 +1138,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): existing system object handle. """ obj = self.__get_object(object_id) - self.check_access(token, "modify_system", obj, attribute) + self.check_access(token, "modify_system", obj, attribute, arg) return self.__call_method(obj, attribute, arg) def modify_repo(self,object_id,attribute,arg,token): @@ -1224,9 +1224,9 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): """ if is_read: - what = "read_kickstart_template": + what = "read_kickstart_template" else: - what = "write_kickstart_template": + what = "write_kickstart_template" self.log(what,name=kickstart_file,token=token) self.check_access(token,what,kickstart_file,is_read) diff --git a/config/acls.conf b/config/acls.conf index 09b82a71..be0caed0 100644 --- a/config/acls.conf +++ b/config/acls.conf @@ -1,66 +1,48 @@ -# the acls.conf file lists specific custom operations to deny to users of certain groups -# when using the authz_configfile or authz_ownership modules. -# -# the denial control flow is: -# -# Authentication module -> Authorization module -> ACL list - - -# Deny nothing from users in the "admin" or "admins" groups - -admin: ~ -admins: ~ - -# Deny nothing from users whose groups are not found in this file: - -unmatched: ~ - -# Example1: users in the group "jradmin" can create/edit/modify systems as long as -# the authorization module lets them. However they do not have permission to create -# new distributions, profiles, image records, or repos. The authorization modules chosen -# in /etc/cobbler/modules.conf are responsible for chosing the group mapping. - +--- +admin: {} +admins: {} jradmin: - "new_distro": ~ - "new_profile": ~ - "new_image": ~ - "new_repo": ~ - "copy_distro": ~ - "copy_profile": ~ - "copy_image": ~ - "copy_repo": ~ - "remove_distro": ~ - "remove_profile": ~ - "remove_image": ~ - "remove_repo": ~ - "modify_distro": ~ - "modify_profile": ~ - "modify_image": ~ - "modify_repo": ~ - "write_kickstart_templates" : ~ - -# Example2: users in group "less trusted" can only modify existing systems that some one -# else creates. If the ownership module is in use, they must also be in the ownership list -# in addition, they cannot manipulate network details of the systems they own. - + copy_distro: {} + copy_image: {} + copy_profile: {} + copy_repo: {} + modify_distro: {} + modify_image: {} + modify_profile: {} + modify_repo: {} + new_distro: {} + new_image: {} + new_profile: {} + new_repo: {} + remove_distro: {} + remove_image: {} + remove_profile: {} + remove_repo: {} + save_distro: {} + save_profile: {} + save_image: {} + save_repo: {} + write_kickstart_templates: {} lesstrusted: - "new_*": ~ - "copy_*": ~ - "remove_*": ~ - "modify_distro": ~ - "modify_profile": ~ - "modify_image": ~ - "modify_repo": ~ - "modify_system": - - "mac-address-*" - - "ip-address-*" - - "hostname-*" - - "gateway-*" - - "subnet-*" - "save_distro": ~ - "save_profile": ~ - "save_image": ~ - "save_repo": ~ - "rename_*": ~ - "sync" : ~ - "write_kickstart_templates" : ~ + copy_*: {} + modify_distro: {} + modify_image: {} + modify_profile: {} + modify_repo: {} + modify_system: + gateway-*: {} + hostname-*: {} + ip-address-*: {} + mac-address-*: {} + subnet-*: {} + new_*: {} + remove_*: {} + rename_*: {} + save_distro: {} + save_image: {} + save_profile: {} + save_repo: {} + sync: {} + write_kickstart_templates: {} +unmatched: {} + diff --git a/config/modules.conf b/config/modules.conf index 6b57d62b..9a483255 100644 --- a/config/modules.conf +++ b/config/modules.conf @@ -52,6 +52,10 @@ module = authn_denyall # # WARNING: this is a security setting, do not choose an option blindly. # +# For modules above that have a concept of groups, /etc/cobbler/acls.conf +# will be enforced after this module is applied. For those that do not +# have a concept of groups (authz_allowall) it will be ignored. +# # for more information: # https://fedorahosted.org/cobbler/wiki/CobblerWebInterface # https://fedorahosted.org/cobbler/wiki/CustomizableSecurity @@ -91,24 +95,3 @@ module = manage_bind [dhcp] module = manage_isc -# configures where ACL data is sourced from. access control -# lists govern what remote features a user can acess based -# on their username or group membership information. -# -# Note that usage of ACLs requires a choice of an authorization module -# that supports ACLs. authz_ownership is one example. An authorization -# module may refuse access based on other critiera /prior/ to consulting -# the access control list. Usage of ACLs with an authorzation module that -# does not support ACLs will have no effect. -# -# choices: -# acls_none -- returns a default ACL list allows all actions -# provided the authorization module does not -# reject access for other reasons (such as ownership) -# acls_configfile -- sources ACL information from /etc/cobbler/acls.conf -# further configuration is required in /etc/cobbler/acls.conf - -[acls] -module = acls_none - - diff --git a/setup.py b/setup.py index 01fa7c9d..c0feebfd 100644 --- a/setup.py +++ b/setup.py @@ -79,6 +79,7 @@ if __name__ == "__main__": (etcpath, ['config/users.digest']), (etcpath, ['config/rsync.exclude']), (etcpath, ['config/users.conf']), + (etcpath, ['config/acls.conf']), (initpath, ['config/cobblerd']), (etcpath, ['config/settings']), # (bashpath, ['config/cobbler_bash']), -- cgit