# HG changeset patch # User Marcus Granado # Date 1259255339 0 # Node ID c9fd958b25ee2e4334558024f64bf0659410e756 # Parent c2e75d891d3dd4542066384cf07331bf727500ca CA-35368: add support to per-key RBAC Signed-off-by: Marcus Granado diff -r c2e75d891d3d -r c9fd958b25ee ocaml/idl/datamodel.ml --- a/ocaml/idl/datamodel.ml Fri Nov 13 14:01:05 2009 +0000 +++ b/ocaml/idl/datamodel.ml Thu Nov 26 17:08:59 2009 +0000 @@ -187,6 +187,7 @@ ?(no_current_operations=false) ?(secret=false) ?(hide_from_docs=false) ?(pool_internal=false) ~allowed_roles + ?(map_keys_roles=[]) ?(params=[]) ?versioned_params () = (* if you specify versioned_params then these get put in the params field of the message record; otherwise params go in with no default values and param_release=call_release... @@ -213,7 +214,8 @@ msg_no_current_operations = no_current_operations; msg_hide_from_docs = hide_from_docs; msg_pool_internal = pool_internal; - msg_allowed_roles = allowed_roles + msg_allowed_roles = allowed_roles; + msg_map_keys_roles = map_keys_roles } let assert_operation_valid enum cls self = call @@ -2537,7 +2539,9 @@ (** Make an object field record *) let field ?(in_oss_since = Some "3.0.3") ?(in_product_since = rel_rio) ?(internal_only = false) ?internal_deprecated_since ?(ignore_foreign_key = false) ?(writer_roles=None) ?(reader_roles=None) - ?(qualifier = RW) ?(ty = String) ?(effect = false) ?(default_value = None) ?(persist = true) name desc = + ?(qualifier = RW) ?(ty = String) ?(effect = false) ?(default_value = None) ?(persist = true) + ?(map_keys_roles=[]) (* list of (key_name,(writer_roles)) for a map field *) + name desc = Field { release={internal=get_product_releases in_product_since; @@ -2552,6 +2556,7 @@ field_ignore_foreign_key = ignore_foreign_key; field_setter_roles = writer_roles; field_getter_roles = reader_roles; + field_map_keys_roles = map_keys_roles; } let uid ?(in_oss_since=Some "3.0.3") ?(reader_roles=None) refname = @@ -2859,7 +2864,7 @@ field ~qualifier:DynamicRO ~ty:String "type" "if the task has completed successfully, this field contains the type of the encoded result (i.e. name of the class whose reference is in the result field). Undefined otherwise."; field ~qualifier:DynamicRO ~ty:String "result" "if the task has completed successfully, this field contains the result value (either Void or an object reference). Undefined otherwise."; field ~qualifier:DynamicRO ~ty:(Set String) "error_info" "if the task has failed, this field contains the set of associated error strings. Undefined otherwise."; - field ~writer_roles:_R_VM_OP ~in_product_since:rel_miami ~default_value:(Some (VMap [])) ~ty:(Map(String, String)) "other_config" "additional configuration"; + field ~in_product_since:rel_miami ~default_value:(Some (VMap [])) ~ty:(Map(String, String)) "other_config" "additional configuration" ~map_keys_roles:[("applies_to",(_R_VM_OP));("XenCenterUUID",(_R_VM_OP))]; (* field ~ty:(Set(Ref _alert)) ~in_product_since:rel_miami ~qualifier:DynamicRO "alerts" "all alerts related to this task"; *) field ~qualifier:DynamicRO ~in_product_since:rel_orlando ~default_value:(Some (VRef "")) ~ty:(Ref _task) "subtask_of" "Ref pointing to the task this is a substask of."; field ~qualifier:DynamicRO ~in_product_since:rel_orlando ~ty:(Set (Ref _task)) "subtasks" "List pointing to all the substasks."; @@ -3481,7 +3486,7 @@ namespace ~name:"API_version" ~contents:api_version (); field ~qualifier:DynamicRO ~ty:Bool "enabled" "True if the host is currently enabled"; field ~qualifier:StaticRO ~ty:(Map(String, String)) "software_version" "version strings"; - field ~ty:(Map(String, String)) "other_config" "additional configuration"; + field ~ty:(Map(String, String)) "other_config" "additional configuration" ~map_keys_roles:[("folder",(_R_VM_OP));("XenCenter.CustomFields.*",(_R_VM_OP))]; field ~qualifier:StaticRO ~ty:(Set(String)) "capabilities" "Xen capabilities"; field ~qualifier:DynamicRO ~ty:(Map(String, String)) "cpu_configuration" "The CPU configuration on this host. May contain keys such as \"nr_nodes\", \"sockets_per_node\", \"cores_per_socket\", or \"threads_per_core\""; field ~qualifier:DynamicRO ~ty:String "sched_policy" "Scheduler policy currently in force on this host"; @@ -3618,7 +3623,7 @@ ] @ (allowed_and_current_operations ~writer_roles:_R_POOL_OP network_operations) @ [ field ~qualifier:DynamicRO ~ty:(Set (Ref _vif)) "VIFs" "list of connected vifs"; field ~qualifier:DynamicRO ~ty:(Set (Ref _pif)) "PIFs" "list of connected pifs"; - field ~writer_roles:_R_POOL_OP ~ty:(Map(String, String)) "other_config" "additional configuration" ; + field ~writer_roles:_R_POOL_OP ~ty:(Map(String, String)) "other_config" "additional configuration" ~map_keys_roles:[("folder",(_R_VM_OP));("XenCenter.CustomFields.*",(_R_VM_OP));("XenCenterCreateInProgress",(_R_VM_OP))]; field ~in_oss_since:None ~qualifier:DynamicRO "bridge" "name of the bridge corresponding to this network on the local host"; field ~qualifier:DynamicRO ~in_product_since:rel_orlando ~ty:(Map(String, Ref _blob)) ~default_value:(Some (VMap [])) "blobs" "Binary blobs associated with this network"; field ~writer_roles:_R_POOL_OP ~in_product_since:rel_orlando ~default_value:(Some (VSet [])) ~ty:(Set String) "tags" "user-specified tags for categorization purposes" @@ -4112,7 +4117,7 @@ field ~qualifier:StaticRO "type" "type of the storage repository"; field ~qualifier:StaticRO "content_type" "the type of the SR's content, if required (e.g. ISOs)"; field ~qualifier:DynamicRO "shared" ~ty:Bool "true if this SR is (capable of being) shared between multiple hosts"; - field ~ty:(Map(String, String)) "other_config" "additional configuration"; + field ~ty:(Map(String, String)) "other_config" "additional configuration" ~map_keys_roles:[("folder",(_R_VM_OP));("XenCenter.CustomFields.*",(_R_VM_OP))]; field ~in_product_since:rel_orlando ~default_value:(Some (VSet [])) ~ty:(Set String) "tags" "user-specified tags for categorization purposes"; field ~ty:Bool ~qualifier:DynamicRO ~in_oss_since:None ~internal_only:true "default_vdi_visibility" ""; field ~in_oss_since:None ~ty:(Map(String, String)) ~in_product_since:rel_miami ~qualifier:RW "sm_config" "SM dependent data" ~default_value:(Some (VMap [])); @@ -4385,7 +4390,7 @@ field ~qualifier:StaticRO ~ty:vdi_type "type" "type of the VDI"; field ~qualifier:StaticRO ~ty:Bool "sharable" "true if this disk may be shared"; field ~qualifier:StaticRO ~ty:Bool "read_only" "true if this disk may ONLY be mounted read-only"; - field ~ty:(Map(String, String)) "other_config" "additional configuration" ; + field ~ty:(Map(String, String)) "other_config" "additional configuration" ~map_keys_roles:[("folder",(_R_VM_OP));("XenCenter.CustomFields.*",(_R_VM_OP))]; field ~qualifier:DynamicRO ~ty:Bool "storage_lock" "true if this disk is locked at the storage level"; (* XXX: location field was in the database in rio, now API in miami *) field ~in_oss_since:None ~in_product_since:rel_miami ~ty:String ~qualifier:DynamicRO ~default_value:(Some (VString "")) "location" "location information"; @@ -5026,7 +5031,7 @@ ; field ~in_oss_since:None ~qualifier:RW ~ty:(Ref _sr) "default_SR" "Default SR for VDIs" ; field ~in_oss_since:None ~qualifier:RW ~ty:(Ref _sr) "suspend_image_SR" "The SR in which VDIs for suspend images are created" ; field ~in_oss_since:None ~qualifier:RW ~ty:(Ref _sr) "crash_dump_SR" "The SR in which VDIs for crash dumps are created" - ; field ~writer_roles:_R_VM_OP ~in_oss_since:None ~ty:(Map(String, String)) "other_config" "additional configuration" + ; field ~in_oss_since:None ~ty:(Map(String, String)) "other_config" "additional configuration" ~map_keys_roles:[("folder",(_R_VM_OP));("XenCenter.CustomFields.*",(_R_VM_OP));("EMPTY_FOLDERS",(_R_VM_OP))] ; field ~in_oss_since:None ~in_product_since:rel_orlando ~qualifier:DynamicRO ~ty:Bool ~default_value:(Some (VBool false)) "ha_enabled" "true if HA is enabled on the pool, false otherwise" ; field ~in_oss_since:None ~in_product_since:rel_orlando ~qualifier:DynamicRO ~ty:(Map(String, String)) ~default_value:(Some (VMap [])) "ha_configuration" "The current HA configuration" ; field ~in_oss_since:None ~in_product_since:rel_orlando ~qualifier:DynamicRO ~ty:(Set String) ~default_value:(Some (VSet [])) "ha_statefiles" "HA statefile VDIs in use" @@ -5437,7 +5442,7 @@ field ~ty:(Map(String, String)) "platform" "platform-specific configuration"; field "PCI_bus" "PCI bus path for pass-through devices"; - field ~ty:(Map(String, String)) "other_config" "additional configuration"; + field ~ty:(Map(String, String)) "other_config" "additional configuration" ~map_keys_roles:[("folder",(_R_VM_OP));("XenCenter.CustomFields.*",(_R_VM_OP))]; field ~qualifier:DynamicRO ~ty:Int "domid" "domain ID (if available, -1 otherwise)"; field ~qualifier:DynamicRO ~in_oss_since:None ~ty:String "domarch" "Domain architecture (if available, null string otherwise)"; field ~in_oss_since:None ~qualifier:DynamicRO ~ty:(Map(String, String)) "last_boot_CPU_flags" "describes the CPU flags on which the VM was last booted"; diff -r c2e75d891d3d -r c9fd958b25ee ocaml/idl/datamodel_types.ml --- a/ocaml/idl/datamodel_types.ml Fri Nov 13 14:01:05 2009 +0000 +++ b/ocaml/idl/datamodel_types.ml Thu Nov 26 17:08:59 2009 +0000 @@ -133,6 +133,7 @@ msg_custom_marshaller: bool; msg_hide_from_docs: bool; msg_allowed_roles: string list option; + msg_map_keys_roles: (string * (string list option)) list } and field = { @@ -148,7 +149,8 @@ field_has_effect: bool; field_ignore_foreign_key: bool; field_setter_roles: string list option; - field_getter_roles: string list option + field_getter_roles: string list option; + field_map_keys_roles: (string * (string list option)) list } and error = { diff -r c2e75d891d3d -r c9fd958b25ee ocaml/idl/datamodel_utils.ml --- a/ocaml/idl/datamodel_utils.ml Fri Nov 13 14:01:05 2009 +0000 +++ b/ocaml/idl/datamodel_utils.ml Thu Nov 26 17:08:59 2009 +0000 @@ -219,7 +219,8 @@ msg_hide_from_docs = false; msg_pool_internal = false; msg_db_only = fld.internal_only; - msg_allowed_roles = None + msg_allowed_roles = None; + msg_map_keys_roles = [] } in let getter = { common with msg_name = prefix "get_"; @@ -283,6 +284,7 @@ "Add the given key-value pair to the %s field of the given %s." (String.concat "/" fld.full_name) x.name); msg_allowed_roles = fld.field_setter_roles; + msg_map_keys_roles = List.map (fun (k,(w))->(k,w)) fld.field_map_keys_roles; msg_tag = FromField(Add, fld) }; { common with msg_name = prefix "remove_from_"; @@ -293,6 +295,7 @@ "Remove the given key and its corresponding value from the %s field of the given %s. If the key is not in that Map, then do nothing." (String.concat "/" fld.full_name) x.name); msg_allowed_roles = fld.field_setter_roles; + msg_map_keys_roles = List.map (fun (k,(w))->(k,w)) fld.field_map_keys_roles; msg_tag = FromField(Remove, fld) }; ] | t, _ -> [ @@ -320,6 +323,7 @@ msg_session=false; msg_release=x.obj_release; msg_has_effect=false; msg_tag=Custom; msg_force_custom = x.force_custom_actions; msg_allowed_roles = None; + msg_map_keys_roles = []; msg_obj_name=x.name } in (* Constructor *) let ctor = { common with diff -r c2e75d891d3d -r c9fd958b25ee ocaml/idl/ocaml_backend/gen_rbac.ml --- a/ocaml/idl/ocaml_backend/gen_rbac.ml Fri Nov 13 14:01:05 2009 +0000 +++ b/ocaml/idl/ocaml_backend/gen_rbac.ml Thu Nov 26 17:08:59 2009 +0000 @@ -79,7 +79,8 @@ let permission_description = "A basic permission" let permission_name wire_name = let s1 =replace_char (Printf.sprintf "permission_%s" wire_name) '.' '_' in - replace_char s1 '/' '_' + let s2 = replace_char s1 '/' '_' in + Stringext.String.replace "*" "WILDCHAR" s2 let permission_index = ref 0 let writer_permission name nperms = @@ -213,8 +214,12 @@ let r,perms = match r1 with []->(xr,[])|r1::_->r1 in concat (xperm,((r,xperm::perms)::r2),extra_rs) +let get_key_permission_name permission key_name = + permission ^ "/key_" ^ key_name + let add_permission_to_roles roles_permissions (obj: obj) (x: message) = let msg_allowed_roles = x.msg_allowed_roles in + let msg_map_keys_roles = x.msg_map_keys_roles in let wire_name = DU.wire_name ~sync:true obj x in match msg_allowed_roles with | None -> ( @@ -222,7 +227,19 @@ (* a message should have at least one role *) failwith (Printf.sprintf "No roles for message %s" wire_name); ) - | Some(allowed_roles) -> concat (wire_name,roles_permissions,allowed_roles) + | Some(allowed_roles) -> + let with_msg_roles_permissions = + (concat (wire_name,roles_permissions,allowed_roles)) + in + List.fold_left + (fun rsps (k,rs)-> + let wire_name_key = get_key_permission_name wire_name k in + match rs with + |None->failwith (Printf.sprintf "No roles for key %s" wire_name_key) + |Some(allowed_roles)->(concat (wire_name_key, rsps, allowed_roles)) + ) + with_msg_roles_permissions + msg_map_keys_roles let get_http_permissions_roles = List.fold_left diff -r c2e75d891d3d -r c9fd958b25ee ocaml/idl/ocaml_backend/gen_server.ml --- a/ocaml/idl/ocaml_backend/gen_server.ml Fri Nov 13 14:01:05 2009 +0000 +++ b/ocaml/idl/ocaml_backend/gen_server.ml Thu Nov 26 17:08:59 2009 +0000 @@ -158,7 +158,8 @@ let rbac_check_begin = if has_session_arg then [ "let arg_names = "^(List.fold_right (fun arg args -> "\""^arg^"\"::"^args) string_args (if is_non_constructor_with_defaults then "\"default_args\"::[]" else "[]"))^" in"; - "Rbac.check session_id __call ~args:(arg_names,__params) ~__context ~fn:(fun ()-> (*RBAC-BEGIN*)"] + "let key_names = "^(List.fold_right (fun arg args -> "\""^arg^"\"::"^args) (List.map (fun (k,_)->k) x.msg_map_keys_roles) "[]")^" in"; + "Rbac.check session_id __call ~args:(arg_names,__params) ~keys:key_names ~__context ~fn:(fun ()-> (*RBAC-BEGIN*)"] else [] in let rbac_check_end = if has_session_arg then [") (*RBAC-END*)"] else [] in diff -r c2e75d891d3d -r c9fd958b25ee ocaml/idl/ocaml_backend/rbac.ml --- a/ocaml/idl/ocaml_backend/rbac.ml Fri Nov 13 14:01:05 2009 +0000 +++ b/ocaml/idl/ocaml_backend/rbac.ml Thu Nov 26 17:08:59 2009 +0000 @@ -95,6 +95,80 @@ session_permissions_tbl session_id +(* create a key permission name that can be in the session *) +let get_key_permission_name permission key_name = + permission ^ "/key_" ^ key_name + +(* create a key-error permission name that is never in the session *) +let get_keyERR_permission_name permission err = + permission ^ "/keyERR_" ^ err + +let permission_of_action ?args ~keys _action = + (* all permissions are in lowercase, see gen_rbac.writer_ *) + let action = (String.lowercase _action) in + if (List.length keys) < 1 + then (* most actions do not use rbac-guarded map keys in the arguments *) + action + + else (* only actions with rbac-guarded map keys fall here *) + match args with + |None -> begin (* this should never happen *) + debug "DENYING access: no args for keyed-action %s" action; + get_keyERR_permission_name action "DENY_NOARGS" (* will always deny *) + end + |Some (arg_keys,arg_values) -> + if (List.length arg_keys) <> (List.length arg_values) + then begin (* this should never happen *) + debug "DENYING access: arg_keys and arg_values lengths don't match: arg_keys=[%s], arg_values=[%s]" + ((List.fold_left (fun ss s->ss^s^",") "" arg_keys)) + ((List.fold_left (fun ss s->ss^(Xml.to_string s)^",") "" arg_values)) + ; + get_keyERR_permission_name action "DENY_WRGLEN" (* will always deny *) + end + else (* keys and values have the same length *) + let rec get_permission_name_of_keys arg_keys arg_values = + match arg_keys,arg_values with + |[],[]|_,[]|[],_-> (* this should never happen *) + begin + debug "DENYING access: no 'key' argument in the action %s" action; + get_keyERR_permission_name action "DENY_NOKEY" (* deny by default *) + end + |k::ks,v::vs-> + if k<>"key" (* "key" is defined in datamodel_utils.ml *) + then + (get_permission_name_of_keys ks vs) + else (* found "key" in args *) + match v with + | Xml.Element("value",_,(Xml.PCData key_name_in_args)::[]) -> + begin + (*debug "key_name_in_args=%s, keys=[%s]" key_name_in_args ((List.fold_left (fun ss s->ss^s^",") "" keys)) ;*) + try + let key_name = + List.find + (fun key_name -> + if Stringext.String.endswith "*" key_name + then begin (* resolve wildcards at the end *) + Stringext.String.startswith + (String.sub key_name 0 ((String.length key_name) - 1)) + key_name_in_args + end + else (* no wildcards to resolve *) + key_name = key_name_in_args + ) + keys + in + get_key_permission_name action (String.lowercase key_name) + with Not_found -> (* expected, key_in_args is not rbac-protected *) + action + end + |_ -> begin (* this should never happen *) + debug "DENYING access: wrong XML value [%s] in the 'key' argument of action %s" (Xml.to_string v) action; + get_keyERR_permission_name action "DENY_NOVALUE" + end + in + get_permission_name_of_keys arg_keys arg_values + + let is_permission_in_session ~session_id ~permission ~session = let find_linear elem set = List.exists (fun e -> e = elem) set in let find_log elem set = Permission_set.mem elem set in @@ -137,10 +211,9 @@ (* Execute fn if rbac access is allowed for action, otherwise fails. *) let nofn = fun () -> () -let check ?(extra_dmsg="") ?(extra_msg="") ?args ~__context ~fn session_id action = +let check ?(extra_dmsg="") ?(extra_msg="") ?args ?(keys=[]) ~__context ~fn session_id action = - (* all permissions are in lowercase, see gen_rbac.writer_ *) - let permission = String.lowercase action in + let permission = permission_of_action action ?args ~keys in if (is_access_allowed ~__context ~session_id ~permission) then (* allow access to action *) diff -r c2e75d891d3d -r c9fd958b25ee ocaml/idl/ocaml_backend/rbac_audit.ml --- a/ocaml/idl/ocaml_backend/rbac_audit.ml Fri Nov 13 14:01:05 2009 +0000 +++ b/ocaml/idl/ocaml_backend/rbac_audit.ml Thu Nov 26 17:08:59 2009 +0000 @@ -299,7 +299,7 @@ with e -> (* never bubble up the error here *) D.debug "ignoring %s" (ExnHelper.string_of_exn e) -let sexpr_of __context session_id allowed_denied ok_error result_error ?args action = +let sexpr_of __context session_id allowed_denied ok_error result_error ?args action permission = let result_error = if result_error = "" then result_error else ":"^result_error in @@ -312,7 +312,7 @@ SExpr.String (ok_error ^ result_error):: SExpr.String (call_type_of action):: (*SExpr.String (Helper_hostname.get_hostname ())::*) - SExpr.String action:: + SExpr.String permission:: (SExpr.Node (sexpr_of_parameters action args)):: [] ) @@ -321,11 +321,11 @@ let fn_append_to_master_audit_log = ref None -let audit_line_of __context session_id allowed_denied ok_error result_error action ?args = +let audit_line_of __context session_id allowed_denied ok_error result_error action permission ?args = let _line = (SExpr.string_of (sexpr_of __context session_id allowed_denied - ok_error result_error ?args action + ok_error result_error ?args action permission ) ) in @@ -339,7 +339,7 @@ let allowed_ok ~__context ~session_id ~action ~permission ?args ?result () = wrap (fun () -> if has_to_audit action then - audit_line_of __context session_id "ALLOWED" "OK" "" action ?args + audit_line_of __context session_id "ALLOWED" "OK" "" action permission ?args ) let allowed_error ~__context ~session_id ~action ~permission ?args ?error () = @@ -350,13 +350,13 @@ | None -> "" | Some error -> (ExnHelper.string_of_exn error) in - audit_line_of __context session_id "ALLOWED" "ERROR" error_str action ?args + audit_line_of __context session_id "ALLOWED" "ERROR" error_str action permission ?args ) let denied ~__context ~session_id ~action ~permission ?args () = wrap (fun () -> if has_to_audit action then - audit_line_of __context session_id "DENIED" "" "" action ?args + audit_line_of __context session_id "DENIED" "" "" action permission ?args ) let session_destroy ~__context ~session_id =