albatross/src/vmm_core.ml

377 lines
11 KiB
OCaml

(* (c) 2017 Hannes Mehnert, all rights reserved *)
open Astring
open Rresult.R.Infix
module I = struct
type t = int
let compare : int -> int -> int = compare
end
module IS = Set.Make(I)
module IM = Map.Make(I)
type permission =
[ `All | `Info | `Image | `Block | `Statistics | `Console | `Log | `Crl ]
let pp_permission ppf = function
| `All -> Fmt.pf ppf "all"
| `Info -> Fmt.pf ppf "info"
| `Image -> Fmt.pf ppf "image"
| `Block -> Fmt.pf ppf "block"
| `Statistics -> Fmt.pf ppf "statistics"
| `Console -> Fmt.pf ppf "console"
| `Log -> Fmt.pf ppf "log"
| `Crl -> Fmt.pf ppf "crl"
let permission_of_string = function
| x when x = "all" -> Some `All
| x when x = "info" -> Some `Info
| x when x = "image" -> Some `Image
| x when x = "block" -> Some `Block
| x when x = "statistics" -> Some `Statistics
| x when x = "console" -> Some `Console
| x when x = "log" -> Some `Log
| x when x = "crl" -> Some `Crl
| _ -> None
type cmd =
[ `Info
| `Destroy_image
| `Create_block
| `Destroy_block
| `Statistics
| `Attach
| `Detach
| `Log
]
let pp_cmd ppf = function
| `Info -> Fmt.pf ppf "info"
| `Destroy_image -> Fmt.pf ppf "destroy"
| `Create_block -> Fmt.pf ppf "create-block"
| `Destroy_block -> Fmt.pf ppf "destroy-block"
| `Statistics -> Fmt.pf ppf "statistics"
| `Attach -> Fmt.pf ppf "attach"
| `Detach -> Fmt.pf ppf "detach"
| `Log -> Fmt.pf ppf "log"
let cmd_of_string = function
| x when x = "info" -> Some `Info
| x when x = "destroy" -> Some `Destroy_image
| x when x = "create-block" -> Some `Create_block
| x when x = "destroy-block" -> Some `Destroy_block
| x when x = "statistics" -> Some `Statistics
| x when x = "attach" -> Some `Attach
| x when x = "detach" -> Some `Detach
| x when x = "log" -> Some `Log
| _ -> None
let cmd_allowed permissions cmd =
List.mem `All permissions ||
let perm = match cmd with
| `Info -> `Info
| `Destroy_image -> `Image
| `Create_block -> `Block
| `Destroy_block -> `Block
| `Statistics -> `Statistics
| `Attach -> `Console
| `Detach -> `Console
| `Log -> `Log
in
List.mem perm permissions
type vmtype = [ `Ukvm_amd64 | `Ukvm_arm64 ]
let pp_vmtype ppf = function
| `Ukvm_amd64 -> Fmt.pf ppf "ukvm-amd64"
| `Ukvm_arm64 -> Fmt.pf ppf "ukvm-arm64"
type id = string list
let string_of_id ids = String.concat ~sep:"." ids
let id_of_string str = String.cuts ~sep:"." str
let drop_super ~super ~sub =
let rec go sup sub = match sup, sub with
| [], xs -> Some (List.rev xs)
| _, [] -> None
| x::xs, z::zs -> if String.equal x z then go xs zs else None
in
go (List.rev super) (List.rev sub)
let is_sub_id ~super ~sub =
match drop_super ~super ~sub with None -> false | Some _ -> true
let pp_id ppf ids =
Fmt.(pf ppf "%a" (list ~sep:(unit ".") string) ids)
let pp_is ppf is = Fmt.pf ppf "%a" Fmt.(list ~sep:(unit ",") int) (IS.elements is)
type bridge = [
| `Internal of string
| `External of string * Ipaddr.V4.t * Ipaddr.V4.t * Ipaddr.V4.t * int
]
let pp_bridge ppf = function
| `Internal name -> Fmt.pf ppf "%s (internal)" name
| `External (name, l, h, gw, nm) ->
Fmt.pf ppf "%s: %a - %a, GW: %a/%d"
name Ipaddr.V4.pp_hum l Ipaddr.V4.pp_hum h Ipaddr.V4.pp_hum gw nm
type delegation = {
vms : int ;
cpuids : IS.t ;
memory : int ;
block : int option ;
bridges : bridge String.Map.t ;
}
let pp_delegation ppf res =
Fmt.pf ppf "delegated: %d vms %a cpus %d MB memory %a MB block bridges: %a"
res.vms pp_is res.cpuids res.memory
Fmt.(option ~none:(unit "no") int) res.block
Fmt.(list ~sep:(unit ", ") pp_bridge)
(List.map snd (String.Map.bindings res.bridges))
let sub_bridges super sub =
String.Map.for_all (fun idx v ->
match String.Map.find idx super, v with
| None, _ -> false
| Some (`Internal nam), `Internal nam' -> String.compare nam nam' = 0
| Some (`External (nam, supf, supl, gw, nm)),
`External (nam', subf, subl, gw', nm') ->
String.compare nam nam' = 0 && nm = nm' &&
Ipaddr.V4.compare supf subf <= 0 && Ipaddr.V4.compare supl subl >= 0
| _ -> false)
sub
let sub_block super sub =
match super, sub with
| None, None -> true
| Some _, None -> true
| Some x, Some y -> x >= y
| None, Some _ -> false
let sub_cpu super sub = IS.subset sub super
let is_sub ~super ~sub =
sub.vms <= super.vms && sub_cpu super.cpuids sub.cpuids &&
sub.memory <= super.memory &&
sub_bridges super.bridges sub.bridges && sub_block super.block sub.block
type vm_config = {
prefix : id ;
vname : string ;
cpuid : int ;
memory : int ;
block_device : string option ;
network : string list ;
vmimage : vmtype * Cstruct.t ;
argv : string list option ;
}
let fullname vm = vm.prefix @ [ vm.vname ]
let filename vm = string_of_id (fullname vm)
(* used for block devices *)
let location vm = match vm.prefix with
| tld::rest -> tld, String.concat ~sep:"." (rest@[vm.vname])
| [] -> invalid_arg "dunno how this happened"
let pp_image ppf (typ, blob) =
let l = Cstruct.len blob in
Fmt.pf ppf "%a: %d bytes" pp_vmtype typ l
let pp_vm_config ppf vm =
Fmt.pf ppf "%s cpu %d, %d MB memory, block device %a@ bridge %a, image %a, argv %a"
vm.vname vm.cpuid vm.memory
Fmt.(option ~none:(unit "no") string) vm.block_device
Fmt.(list ~sep:(unit ", ") string) vm.network
pp_image vm.vmimage
Fmt.(option ~none:(unit "no") (list ~sep:(unit " ") string)) vm.argv
let good_bridge idxs nets =
(* TODO: uniqueness of n -- it should be an ordered set? *)
List.for_all (fun n -> String.Map.mem n nets) idxs
let vm_matches_res (res : delegation) (vm : vm_config) =
res.vms >= 1 && IS.mem vm.cpuid res.cpuids &&
vm.memory <= res.memory &&
good_bridge vm.network res.bridges
let check_policies vm res =
let rec climb = function
| super :: sub :: xs ->
if is_sub ~super ~sub then climb (sub :: xs)
else Error (`Msg "policy violation")
| [x] -> Ok x
| [] -> Error (`Msg "empty resource list")
in
climb res >>= fun res ->
if vm_matches_res res vm then Ok () else Error (`Msg "VM does not match policy")
type vm = {
config : vm_config ;
cmd : Bos.Cmd.t ;
pid : int ;
taps : string list ;
stdout : Unix.file_descr (* ringbuffer thingy *)
}
let pp_vm ppf vm =
Fmt.pf ppf "pid %d@ taps %a cmdline %a"
vm.pid Fmt.(list ~sep:(unit ", ") string) vm.taps
Bos.Cmd.pp vm.cmd
let identifier serial =
match Hex.of_cstruct @@ Nocrypto.Hash.SHA256.digest @@
Nocrypto.Numeric.Z.to_cstruct_be @@ serial
with
| `Hex str -> fst (String.span ~max:6 str)
let id cert = identifier (X509.serial cert)
let parse_db lines =
List.fold_left (fun acc s ->
acc >>= fun datas ->
match String.cut ~sep:" " s with
| None -> Rresult.R.error_msgf "unable to parse entry %s" s
| Some (a, b) ->
(try Ok (Z.of_string a) with Invalid_argument x -> Error (`Msg x)) >>= fun s ->
Ok ((s, b) :: datas))
(Ok []) lines
let find_in_db label db tst =
try Ok (List.find tst db)
with Not_found -> Rresult.R.error_msgf "couldn't find %s in database" label
let find_name db name =
find_in_db name db (fun (_, n) -> String.equal n name) >>= fun (serial, _) ->
Ok serial
let translate_serial db serial =
let tst (s, _) = String.equal serial (identifier s) in
match find_in_db "" db tst with
| Ok (_, n) -> n
| Error _ -> serial
let translate_name db name =
match find_name db name with
| Ok serial -> identifier serial
| Error _ -> name
(* this separates the leaf and top-level certificate from the chain,
and also reverses the intermediates (to be (leaf, CA -> subCA -> subCA')
in which subCA' signed leaf *)
let separate_chain = function
| [] -> Error (`Msg "empty chain")
| [ leaf ] -> Ok (leaf, [])
| leaf :: xs -> Ok (leaf, List.rev xs)
type rusage = {
utime : (int64 * int) ;
stime : (int64 * int) ;
maxrss : int64 ;
ixrss : int64 ;
idrss : int64 ;
isrss : int64 ;
minflt : int64 ;
majflt : int64 ;
nswap : int64 ;
inblock : int64 ;
outblock : int64 ;
msgsnd : int64 ;
msgrcv : int64 ;
nsignals : int64 ;
nvcsw : int64 ;
nivcsw : int64 ;
}
let pp_rusage ppf r =
Fmt.pf ppf "utime %Lu.%d stime %Lu.%d maxrss %Lu ixrss %Lu idrss %Lu isrss %Lu minflt %Lu majflt %Lu nswap %Lu inblock %Lu outblock %Lu msgsnd %Lu msgrcv %Lu signals %Lu nvcsw %Lu nivcsw %Lu"
(fst r.utime) (snd r.utime) (fst r.stime) (snd r.stime) r.maxrss r.ixrss r.idrss r.isrss r.minflt r.majflt r.nswap r.inblock r.outblock r.msgsnd r.msgrcv r.nsignals r.nvcsw r.nivcsw
type ifdata = {
name : string ;
flags : int32 ;
send_length : int32 ;
max_send_length : int32 ;
send_drops : int32 ;
mtu : int32 ;
baudrate : int64 ;
input_packets : int64 ;
input_errors : int64 ;
output_packets : int64 ;
output_errors : int64 ;
collisions : int64 ;
input_bytes : int64 ;
output_bytes : int64 ;
input_mcast : int64 ;
output_mcast : int64 ;
input_dropped : int64 ;
output_dropped : int64 ;
}
let pp_ifdata ppf i =
Fmt.pf ppf "name %s flags %lX send_length %lu max_send_length %lu send_drops %lu mtu %lu baudrate %Lu input_packets %Lu input_errors %Lu output_packets %Lu output_errors %Lu collisions %Lu input_bytes %Lu output_bytes %Lu input_mcast %Lu output_mcast %Lu input_dropped %Lu output_dropped %Lu"
i.name i.flags i.send_length i.max_send_length i.send_drops i.mtu i.baudrate i.input_packets i.input_errors i.output_packets i.output_errors i.collisions i.input_bytes i.output_bytes i.input_mcast i.output_mcast i.input_dropped i.output_dropped
module Log = struct
type hdr = {
ts : Ptime.t ;
context : id ;
name : string ;
}
let pp_hdr db ppf hdr =
let name = translate_serial db hdr.name in
Fmt.pf ppf "%a: %s" (Ptime.pp_human ~tz_offset_s:0 ()) hdr.ts name
let hdr context name = { ts = Ptime_clock.now () ; context ; name }
type event =
[ `Startup
| `Login of Ipaddr.V4.t * int
| `Logout of Ipaddr.V4.t * int
| `VM_start of int * string list * string option
| `VM_stop of int * [ `Exit of int | `Signal of int | `Stop of int ]
| `Block_create of string * int
| `Block_destroy of string
| `Delegate of string list * string option
(* | `CRL of string *)
]
let pp_event ppf = function
| `Startup -> Fmt.(pf ppf "STARTUP")
| `Login (ip, port) -> Fmt.pf ppf "LOGIN %a:%d" Ipaddr.V4.pp_hum ip port
| `Logout (ip, port) -> Fmt.pf ppf "LOGOUT %a:%d" Ipaddr.V4.pp_hum ip port
| `VM_start (pid, taps, block) ->
Fmt.pf ppf "STARTED %d (tap %a, block %a)"
pid Fmt.(list ~sep:(unit "; ") string) taps
Fmt.(option ~none:(unit "no") string) block
| `VM_stop (pid, code) ->
let s, c = match code with
| `Exit n -> "exit", n
| `Signal n -> "signal", n
| `Stop n -> "stop", n
in
Fmt.pf ppf "STOPPED %d with %s %d" pid s c
| `Block_create (name, size) ->
Fmt.pf ppf "BLOCK_CREATE %s %d" name size
| `Block_destroy name -> Fmt.pf ppf "BLOCK_DESTROY %s" name
| `Delegate (bridges, block) ->
Fmt.pf ppf "DELEGATE %a, block %a"
Fmt.(list ~sep:(unit "; ") string) bridges
Fmt.(option ~none:(unit "no") string) block
(* | `CRL of string *)
type msg = hdr * event
let pp db ppf (hdr, event) =
Fmt.pf ppf "%a %a" (pp_hdr db) hdr pp_event event
end