Skip to content

Commit 7e80ad2

Browse files
authored
Merge master into feature/trusted-certs (#7043)
2 parents 81be3d9 + e945d08 commit 7e80ad2

12 files changed

Lines changed: 386 additions & 359 deletions

ocaml/idl/datamodel_vm.ml

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2384,6 +2384,105 @@ let sysprep =
23842384
as part of a reboot."
23852385
~allowed_roles:_R_VM_ADMIN ()
23862386

2387+
module Other_config = struct
2388+
let protected_keys =
2389+
[
2390+
("hvm_serial", _R_POOL_ADMIN)
2391+
; ("pci", _R_POOL_ADMIN)
2392+
; ("folder", _R_VM_OP)
2393+
; ("XenCenter.CustomFields.*", _R_VM_OP)
2394+
]
2395+
2396+
let call =
2397+
call
2398+
~lifecycle:[(Published, rel_rio, "additional configuration")]
2399+
~allowed_roles:_R_VM_ADMIN
2400+
2401+
let add_to_other_config =
2402+
call ~name:"add_to_other_config"
2403+
~doc:
2404+
"Add the given key-value pair to the other_config field of the given \
2405+
VM."
2406+
~params:
2407+
[
2408+
(Ref _vm, "self", "reference to object")
2409+
; (String, "key", "Key to add")
2410+
; (String, "value", "Value to add")
2411+
]
2412+
~map_keys_roles:protected_keys ~flags:[`Session] ()
2413+
2414+
let remove_from_other_config =
2415+
call ~name:"remove_from_other_config"
2416+
~doc:
2417+
"Remove the given key and its corresponding value from the \
2418+
other_config field of the given VM. If the key is not in that Map, \
2419+
then do nothing."
2420+
~params:
2421+
[
2422+
(Ref _vm, "self", "reference to object")
2423+
; (String, "key", "Key of entry to remove")
2424+
]
2425+
~map_keys_roles:protected_keys ~flags:[`Session] ()
2426+
2427+
(* map_keys_roles can't be cited here, since they're only implemented for
2428+
{add_to,remove_from}_other_config, RBAC handling is done in a manual
2429+
implementation. *)
2430+
let set_other_config =
2431+
call ~name:"set_other_config"
2432+
~doc:"Set the other_config field of the given VM."
2433+
~params:
2434+
[
2435+
(Ref _vm, "self", "reference to object")
2436+
; (Map (String, String), "value", "New value to set")
2437+
]
2438+
~flags:[`Session] ()
2439+
end
2440+
2441+
module Platform = struct
2442+
let protected_keys = [("hvm_serial", _R_POOL_ADMIN)]
2443+
2444+
let call =
2445+
call
2446+
~lifecycle:[(Published, rel_rio, "platform-specific configuration")]
2447+
~allowed_roles:_R_VM_ADMIN
2448+
2449+
let add_to_platform =
2450+
call ~name:"add_to_platform"
2451+
~doc:"Add the given key-value pair to the platform field of the given VM."
2452+
~params:
2453+
[
2454+
(Ref _vm, "self", "reference to object")
2455+
; (String, "key", "Key to add")
2456+
; (String, "value", "Value to add")
2457+
]
2458+
~map_keys_roles:protected_keys ~flags:[`Session] ()
2459+
2460+
let remove_from_platform =
2461+
call ~name:"remove_from_platform"
2462+
~doc:
2463+
"Remove the given key and its corresponding value from the platform \
2464+
field of the given VM. If the key is not in that Map, then do \
2465+
nothing."
2466+
~params:
2467+
[
2468+
(Ref _vm, "self", "reference to object")
2469+
; (String, "key", "Key of entry to remove")
2470+
]
2471+
~map_keys_roles:protected_keys ~flags:[`Session] ()
2472+
2473+
(* map_keys_roles can't be cited here, since they're only implemented for
2474+
{add_to,remove_from}_platform, RBAC handling is done in a manual
2475+
implementation. *)
2476+
let set_platform =
2477+
call ~name:"set_platform" ~doc:"Set the platform field of the given VM."
2478+
~params:
2479+
[
2480+
(Ref _vm, "self", "reference to object")
2481+
; (Map (String, String), "value", "New value to set")
2482+
]
2483+
~flags:[`Session] ()
2484+
end
2485+
23872486
let vm_uefi_mode =
23882487
Enum
23892488
( "vm_uefi_mode"
@@ -2587,6 +2686,12 @@ let t =
25872686
; add_to_blocked_operations
25882687
; remove_from_blocked_operations
25892688
; sysprep
2689+
; Other_config.add_to_other_config
2690+
; Other_config.remove_from_other_config
2691+
; Other_config.set_other_config
2692+
; Platform.add_to_platform
2693+
; Platform.remove_from_platform
2694+
; Platform.set_platform
25902695
]
25912696
~contents:
25922697
([
@@ -2715,9 +2820,10 @@ let t =
27152820
~qualifier:DynamicRO ~ty:(Set (Ref _vtpm)) "VTPMs" "virtual TPMs"
27162821
; namespace ~name:"PV" ~contents:pv ()
27172822
; namespace ~name:"HVM" ~contents:hvm ()
2718-
; field
2823+
; field ~qualifier:StaticRO
27192824
~ty:(Map (String, String))
27202825
~lifecycle:[(Published, rel_rio, "platform-specific configuration")]
2826+
~map_keys_roles:[("hvm_serial", _R_POOL_ADMIN)]
27212827
"platform" "platform-specific configuration"
27222828
; field
27232829
~lifecycle:
@@ -2726,13 +2832,14 @@ let t =
27262832
; (Deprecated, rel_boston, "Field was never used")
27272833
]
27282834
"PCI_bus" "PCI bus path for pass-through devices"
2729-
; field
2835+
; field ~qualifier:StaticRO
27302836
~lifecycle:[(Published, rel_rio, "additional configuration")]
27312837
~ty:(Map (String, String))
27322838
"other_config" "additional configuration"
27332839
~map_keys_roles:
27342840
[
27352841
("pci", _R_POOL_ADMIN)
2842+
; ("hvm_serial", _R_POOL_ADMIN)
27362843
; ("folder", _R_VM_OP)
27372844
; ("XenCenter.CustomFields.*", _R_VM_OP)
27382845
]

ocaml/idl/schematest.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ let hash x = Digest.string x |> Digest.to_hex
33
(* BEWARE: if this changes, check that schema has been bumped accordingly in
44
ocaml/idl/datamodel_common.ml, usually schema_minor_vsn *)
55

6-
let last_known_schema_hash = "4f2ba6e138f25124b8f211af96516805"
6+
let last_known_schema_hash = "2d8501063ef6b243facc24a3dbdc2a5d"
77

88
let current_schema_hash : string =
99
let open Datamodel_types in

ocaml/quicktest/quicktest_http.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ module Cookies = struct
102102
match body with
103103
| first_line :: _ ->
104104
D.warn "expected = [%s]; received = [%s]" expected first_line ;
105-
Astring.String.is_infix ~affix:first_line expected
105+
Astring.String.is_infix ~affix:expected first_line
106106
| _ ->
107107
false
108108
in

ocaml/xapi/helpers.ml

Lines changed: 183 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ module TraceHelper = struct
428428
end
429429

430430
(** Once the server functor has been instantiated, xapi sets this reference to the appropriate
431-
"fake_rpc" (loopback non-HTTP) rpc function.
431+
"fake_rpc" (loopback non-HTTP) rpc function.
432432
This way, internally the coordinator can short-circuit API calls without having to go over the network. *)
433433
let rpc_fun : (Http.Request.t -> Rpc.call -> Rpc.response) option ref = ref None
434434

@@ -2462,3 +2462,185 @@ module AuthenticationCache = struct
24622462
None
24632463
end
24642464
end
2465+
2466+
(* Simple trie data structure that performs a favoured lookup to
2467+
implement a simple form of wildcard key matching. The trie is not
2468+
pruned during (or after) construction. *)
2469+
module MatchTrie = struct
2470+
type 'a node = {arrows: (string, 'a node) Hashtbl.t; mutable value: 'a option}
2471+
2472+
let create_node () =
2473+
let arrows = Hashtbl.create 16 in
2474+
let value = None in
2475+
{arrows; value}
2476+
2477+
let create = create_node
2478+
2479+
let insert root ~key ~value =
2480+
let parts = String.split_on_char '.' key in
2481+
let rec extend focused = function
2482+
| part :: parts ->
2483+
let next =
2484+
match Hashtbl.find_opt focused.arrows part with
2485+
| Some node ->
2486+
node
2487+
| _ ->
2488+
let next = create_node () in
2489+
Hashtbl.replace focused.arrows part next ;
2490+
next
2491+
in
2492+
extend next parts
2493+
| [] ->
2494+
focused
2495+
in
2496+
let final = extend root parts in
2497+
final.value <- Some value
2498+
2499+
let find root ~key =
2500+
let parts = String.split_on_char '.' key in
2501+
let rec find focused = function
2502+
| part :: parts -> (
2503+
(* Wildcard edges override other edges. *)
2504+
match Hashtbl.find_opt focused.arrows "*" with
2505+
| Some _ as sink ->
2506+
sink
2507+
| _ -> (
2508+
match Hashtbl.find_opt focused.arrows part with
2509+
| Some next ->
2510+
(find [@tailcall]) next parts
2511+
| _ ->
2512+
None
2513+
)
2514+
)
2515+
| _ ->
2516+
Some focused
2517+
in
2518+
match find root parts with Some node -> node.value | _ -> None
2519+
end
2520+
2521+
(* Given an input key, compare against the protected keys of the
2522+
task.other_config field. If a protected key matches, return it.
2523+
2524+
For example, if the datamodel specifies "foo.bar.*" as a protected
2525+
key, then: match_protected_key ~key:"foo.bar.baz" = Some "foo.bar.*".
2526+
2527+
It must return the protected key as that is what key-related RBAC
2528+
entries are defined in terms of.
2529+
*)
2530+
let match_protected_key objname fieldname =
2531+
(* Attain the listing of protected keys from the datamodel at module
2532+
initialisation. Usually, this list is passed to Rbac.check by
2533+
handlers inside the auto-generated server.ml file. *)
2534+
let protected_keys =
2535+
let api = Datamodel.all_api in
2536+
let field = Dm_api.get_field_by_name api ~objname ~fieldname in
2537+
List.map fst field.field_map_keys_roles
2538+
in
2539+
(* Define the lookup function in terms of a simple trie data
2540+
structure - which is flexible to account for overlapping paths and
2541+
presence of wildcards. *)
2542+
let trie =
2543+
let root = MatchTrie.create () in
2544+
let add key = MatchTrie.insert root ~key ~value:key in
2545+
List.iter add protected_keys ;
2546+
root
2547+
in
2548+
MatchTrie.find trie
2549+
2550+
(* The behaviour of this function, with respect to RBAC checking, must
2551+
match serial "remove_from" and "add_to" operations (for only the keys
2552+
that are changing).
2553+
2554+
There is normally no key-related RBAC checking for
2555+
"set_X" (e.g. set_other_config) because the required writer role for the
2556+
entire field is usually higher than the role(s) required for
2557+
individually-protected keys.
2558+
2559+
{Task,VM}.set_other_config and VM.set_platform are special cases where
2560+
lower-privileged sessions must be able to manipulate a subset of entries
2561+
(those not protected by a more privileged role).
2562+
*)
2563+
let set_map_with_rbac ~__context ~self ~value ~get_fn ~set_fn ~match_protected
2564+
~object_name ~field_name =
2565+
let match_protected = match_protected field_name in
2566+
let module S = Set.Make (String) in
2567+
let create_lookup kvs =
2568+
let table = List.to_seq kvs |> Hashtbl.of_seq in
2569+
Hashtbl.find_opt table
2570+
in
2571+
let old_value = get_fn ~__context ~self in
2572+
let lookup_old, lookup_new = (create_lookup old_value, create_lookup value) in
2573+
let keys_before, keys_after =
2574+
let keys = List.map fst in
2575+
let before = keys old_value in
2576+
let after = keys value in
2577+
S.(of_list before, of_list after)
2578+
in
2579+
let keys_removed =
2580+
(* Keys no longer appearing in the map. The user must have the
2581+
"remove_from" role for each of the protected keys in the set. *)
2582+
S.diff keys_before keys_after
2583+
in
2584+
let keys_unchanged =
2585+
(* Keys that persist across the update. If any key in this set is
2586+
protected AND the value mapped to by the key would be changed by
2587+
the update, the session must have the "add_to" role. *)
2588+
let updated = S.inter keys_before keys_after in
2589+
let is_entry_unchanged key =
2590+
let is_same =
2591+
let ( let* ) = Option.bind in
2592+
let* old_value = lookup_old key in
2593+
let* new_value = lookup_new key in
2594+
Some (old_value = new_value)
2595+
in
2596+
Option.value ~default:false is_same
2597+
in
2598+
(* Filter out the unchanged entries, as you don't need any
2599+
extra privileges to maintain an entry that's already there. *)
2600+
S.filter is_entry_unchanged updated
2601+
in
2602+
let keys_added =
2603+
(* Treat all keys as new, unless they're referring to entries that
2604+
are unchanged across the update. *)
2605+
S.diff keys_after keys_unchanged
2606+
in
2607+
let permissions =
2608+
(* Map each of the added and removed keys to protected keys, if
2609+
such a key exists. *)
2610+
let filter keys =
2611+
S.filter_map (fun key -> match_protected ~key) keys |> S.elements
2612+
in
2613+
let added = filter keys_added in
2614+
let removed = filter keys_removed in
2615+
let format operation key =
2616+
(* All the permissions are stored in lowercase. *)
2617+
let key = String.lowercase_ascii key in
2618+
Printf.sprintf "%s.%s_%s/key:%s" object_name operation field_name key
2619+
in
2620+
(* The required permissions are defined in terms of those
2621+
generated for "add_to" and "remove_from" (both implemented
2622+
above). They can be defined as custom AND use RBAC checking within
2623+
server.ml because their operation is purely destructive, so it's
2624+
sufficient to guard the entire action with Rbac.check. *)
2625+
let added_perms = List.map (format "add_to") added in
2626+
let removed_perms = List.map (format "remove_from") removed in
2627+
added_perms @ removed_perms
2628+
in
2629+
(* Find the first disallowed permission, indicating that we cannot
2630+
perform the action. *)
2631+
let session_id = Context.get_session_id __context in
2632+
match
2633+
Rbac.find_first_disallowed_permission ~__context ~session_id ~permissions
2634+
with
2635+
| None ->
2636+
(* No disallowed permission, perform the update. *)
2637+
set_fn ~__context ~self ~value
2638+
| Some disallowed ->
2639+
(* Report it as an RBAC error. *)
2640+
let action = Printf.sprintf "%s.set_%s" object_name field_name in
2641+
let extra_msg = "" in
2642+
let extra_dmsg = "" in
2643+
raise
2644+
(Rbac.disallowed_permission_exn ~extra_dmsg ~extra_msg ~__context
2645+
~permission:disallowed ~action
2646+
)

ocaml/xapi/message_forwarding.ml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,6 +3245,34 @@ functor
32453245
~policy (fun () ->
32463246
forward_vm_op ~local_fn ~__context ~vm:self ~remote_fn
32473247
)
3248+
3249+
let add_to_other_config ~__context ~self ~key ~value =
3250+
info "VM.add_to_other_config: self = '%s', key = '%s'"
3251+
(vm_uuid ~__context self) key ;
3252+
Local.VM.add_to_other_config ~__context ~self ~key ~value
3253+
3254+
let remove_from_other_config ~__context ~self ~key =
3255+
info "VM.remove_from_other_config: self = '%s', key = '%s'"
3256+
(vm_uuid ~__context self) key ;
3257+
Local.VM.remove_from_other_config ~__context ~self ~key
3258+
3259+
let set_other_config ~__context ~self ~value =
3260+
info "VM.set_other_config: self = '%s'" (vm_uuid ~__context self) ;
3261+
Local.VM.set_other_config ~__context ~self ~value
3262+
3263+
let add_to_platform ~__context ~self ~key ~value =
3264+
info "VM.add_to_platform: self = '%s', key = '%s'"
3265+
(vm_uuid ~__context self) key ;
3266+
Local.VM.add_to_platform ~__context ~self ~key ~value
3267+
3268+
let remove_from_platform ~__context ~self ~key =
3269+
info "VM.remove_from_platform: self = '%s', key = '%s'"
3270+
(vm_uuid ~__context self) key ;
3271+
Local.VM.remove_from_platform ~__context ~self ~key
3272+
3273+
let set_platform ~__context ~self ~value =
3274+
info "VM.set_platform: self = '%s'" (vm_uuid ~__context self) ;
3275+
Local.VM.set_platform ~__context ~self ~value
32483276
end
32493277

32503278
module VM_metrics = struct end

0 commit comments

Comments
 (0)