From acf46b423185a5faad5c95700bf17a24ca127358 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Thu, 11 Apr 2019 12:25:19 +0100 Subject: [PATCH 1/6] Allow naming hosts and add examples to rules.ml Previously we passed in the interface, from which it was possible (but a little difficult) to extract the IP address and compare with some predefined ones. Now, we allow the user to list IP addresses and named tags for them, which can be matched on easily. Added example rules showing how to block access to an external service or allow SSH between AppVMs. Requested at https://groups.google.com/d/msg/qubes-users/BnL0nZGpJOE/61HOBg1rCgAJ. --- firewall.ml | 14 +++++++++++++- packet.ml | 12 +++++++++--- rules.ml | 26 +++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/firewall.ml b/firewall.ml index 39254d3..0e38d45 100644 --- a/firewall.ml +++ b/firewall.ml @@ -125,9 +125,21 @@ let nat_to t ~host ~port packet = (* Handle incoming packets *) +let parse_ips ips = List.map (fun (ip_str, id) -> (Ipaddr.of_string_exn ip_str, id)) ips + +let clients = parse_ips Rules.clients +let externals = parse_ips Rules.externals + +let resolve_host = function + | `Client c -> `Client (try List.assoc (Ipaddr.V4 c#other_ip) clients with Not_found -> `Unknown) + | `External ip -> `External (try List.assoc ip externals with Not_found -> `Unknown) + | (`Client_gateway | `Firewall_uplink | `NetVM) as x -> x + let apply_rules t rules info = let packet = info.packet in - match rules info, info.dst with + let resolved_info = { info with src = resolve_host info.src; + dst = resolve_host info.dst } in + match rules resolved_info, info.dst with | `Accept, `Client client_link -> transmit_ipv4 packet client_link | `Accept, (`External _ | `NetVM) -> transmit_ipv4 packet t.Router.uplink | `Accept, (`Firewall_uplink | `Client_gateway) -> diff --git a/packet.ml b/packet.ml index a9fa4e7..607fd37 100644 --- a/packet.ml +++ b/packet.ml @@ -13,9 +13,15 @@ type ports = { type host = [ `Client of client_link | `Client_gateway | `Firewall_uplink | `NetVM | `External of Ipaddr.t ] -type info = { +(* Note: 'a is either [host], or the result of applying [Rules.clients] and [Rules.externals] to a host. *) +type 'a info = { packet : Nat_packet.t; - src : host; - dst : host; + src : 'a; + dst : 'a; proto : [ `UDP of ports | `TCP of ports | `ICMP | `Unknown ]; } + +(* The first message in a TCP connection has SYN set and ACK clear. *) +let is_tcp_start = function + | `IPv4 (_ip, `TCP (hdr, _body)) -> Tcp.Tcp_packet.(hdr.syn && not hdr.ack) + | _ -> false diff --git a/rules.ml b/rules.ml index 7e62790..7980469 100644 --- a/rules.ml +++ b/rules.ml @@ -25,13 +25,37 @@ open Packet - [`Drop reason] drop the packet and log the reason. *) +(* List your AppVM IP addresses here if you want to match on them in the rules below. + Any client not listed here will appear as [`Client `Unknown]. *) +let clients = [ + (* + "10.137.0.12", `Dev; + "10.137.0.14", `Untrusted; + *) +] + +(* List your external (non-AppVM) IP addresses here if you want to match on them in the rules below. + Any external machine not listed here will appear as [`External `Unknown]. *) +let externals = [ + (* + "8.8.8.8", `GoogleDNS; + *) +] + (** Decide what to do with a packet from a client VM. Note: If the packet matched an existing NAT rule then this isn't called. *) let from_client = function + (* Examples (add your own rules here): *) + (* + | { src = `Client `Dev; dst = `Client `Untrusted; proto = `TCP { dport = 22 } } -> `Accept + | { src = `Client _; dst = `Client _; proto = `TCP _; packet } + when not (is_tcp_start packet) -> `Accept + | { dst = `External `GoogleDNS } -> `Drop "block Google DNS" + *) | { dst = (`External _ | `NetVM) } -> `NAT | { dst = `Client_gateway; proto = `UDP { dport = 53 } } -> `NAT_to (`NetVM, 53) | { dst = (`Client_gateway | `Firewall_uplink) } -> `Drop "packet addressed to firewall itself" - | { dst = `Client _ } -> `Drop "prevent communication between client VMs" + | { dst = `Client _ } -> `Drop "prevent communication between client VMs by default" (** Decide what to do with a packet received from the outside world. Note: If the packet matched an existing NAT rule then this isn't called. *) From 189a7363680c2f0075a4c730d493f5321f04c122 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Wed, 17 Apr 2019 10:26:32 +0100 Subject: [PATCH 2/6] Add some types to the rules Before, we inferred the types from rules.ml and then the compiler checked that it was consistent with what firewall.ml expected. If it wasn't it reported the problem as being with firewall.ml, which could be confusing to users. --- packet.ml | 11 +++++++++++ rules.ml | 23 ++++------------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/packet.ml b/packet.ml index 607fd37..97f1feb 100644 --- a/packet.ml +++ b/packet.ml @@ -25,3 +25,14 @@ type 'a info = { let is_tcp_start = function | `IPv4 (_ip, `TCP (hdr, _body)) -> Tcp.Tcp_packet.(hdr.syn && not hdr.ack) | _ -> false + +(* The possible actions we can take for a packet: *) +type action = [ + | `Accept (* Send the packet to its destination. *) + | `NAT (* Rewrite the packet's source field so packet appears to + have come from the firewall, via an unused port. + Also, add NAT rules so related packets will be translated accordingly. *) + | `NAT_to of host * port (* As for [`NAT], but also rewrite the packet's + destination fields so it will be sent to [host:port]. *) + | `Drop of string (* Drop the packet and log the given reason. *) +] diff --git a/rules.ml b/rules.ml index 7980469..352c98b 100644 --- a/rules.ml +++ b/rules.ml @@ -8,23 +8,6 @@ open Packet (* OCaml normally warns if you don't match all fields, but that's OK here. *) [@@@ocaml.warning "-9"] -(** {2 Actions} - - The possible actions are: - - - [`Accept] : Send the packet to its destination. - - - [`NAT] : Rewrite the packet's source field so packet appears to - have come from the firewall, via an unused port. - Also, add NAT rules so related packets will be translated accordingly. - - - [`NAT_to (host, port)] : - As for [`NAT], but also rewrite the packet's destination fields so it - will be sent to [host:port]. - - - [`Drop reason] drop the packet and log the reason. -*) - (* List your AppVM IP addresses here if you want to match on them in the rules below. Any client not listed here will appear as [`Client `Unknown]. *) let clients = [ @@ -44,7 +27,8 @@ let externals = [ (** Decide what to do with a packet from a client VM. Note: If the packet matched an existing NAT rule then this isn't called. *) -let from_client = function +let from_client (info : _ info) : action = + match info with (* Examples (add your own rules here): *) (* | { src = `Client `Dev; dst = `Client `Untrusted; proto = `TCP { dport = 22 } } -> `Accept @@ -59,5 +43,6 @@ let from_client = function (** Decide what to do with a packet received from the outside world. Note: If the packet matched an existing NAT rule then this isn't called. *) -let from_netvm = function +let from_netvm (info : _ info) : action = + match info with | _ -> `Drop "drop by default" From b60d098e96b2b713589d51748cc06e387f92519c Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Wed, 17 Apr 2019 11:03:17 +0100 Subject: [PATCH 3/6] Give exact types for Packet.src Before, the packet passed to rules.ml could have any host as its src. Now, `from_client` knows that `src` must be a `Client`, and `from_netvm` knows that `src` is `External` or `NetVM`. --- client_net.ml | 8 +++--- firewall.ml | 67 +++++++++++++++++++++++++++++---------------------- firewall.mli | 2 +- packet.ml | 7 +++--- rules.ml | 33 ++++++++++++++++++------- 5 files changed, 70 insertions(+), 47 deletions(-) diff --git a/client_net.ml b/client_net.ml index 0649567..68fe6d3 100644 --- a/client_net.ml +++ b/client_net.ml @@ -56,7 +56,7 @@ let input_arp ~fixed_arp ~iface request = iface#writev `ARP (fun b -> Arp_packet.encode_into response b; Arp_packet.size) (** Handle an IPv4 packet from the client. *) -let input_ipv4 ~client_ip ~router packet = +let input_ipv4 ~iface ~router packet = match Nat_packet.of_ipv4_packet packet with | Error e -> Log.warn (fun f -> f "Ignored unknown IPv4 message: %a" Nat_packet.pp_error e); @@ -64,10 +64,10 @@ let input_ipv4 ~client_ip ~router packet = | Ok packet -> let `IPv4 (ip, _) = packet in let src = ip.Ipv4_packet.src in - if src = client_ip then Firewall.ipv4_from_client router packet + if src = iface#other_ip then Firewall.ipv4_from_client router ~src:iface packet else ( Log.warn (fun f -> f "Incorrect source IP %a in IP packet from %a (dropping)" - Ipaddr.V4.pp src Ipaddr.V4.pp client_ip); + Ipaddr.V4.pp src Ipaddr.V4.pp iface#other_ip); return () ) @@ -94,7 +94,7 @@ let add_vif { Dao.ClientVif.domid; device_id } ~client_ip ~router ~cleanup_tasks | Ok (eth, payload) -> match eth.Ethernet_packet.ethertype with | `ARP -> input_arp ~fixed_arp ~iface payload - | `IPv4 -> input_ipv4 ~client_ip ~router payload + | `IPv4 -> input_ipv4 ~iface ~router payload | `IPv6 -> return () (* TODO: oh no! *) ) >|= or_raise "Listen on client interface" Netback.pp_error diff --git a/firewall.ml b/firewall.ml index 0e38d45..cbb47b7 100644 --- a/firewall.ml +++ b/firewall.ml @@ -48,8 +48,21 @@ let forward_ipv4 t packet = (* Packet classification *) -let classify t packet = - let `IPv4 (ip, transport) = packet in +let parse_ips ips = List.map (fun (ip_str, id) -> (Ipaddr.of_string_exn ip_str, id)) ips + +let clients = parse_ips Rules.clients +let externals = parse_ips Rules.externals + +let resolve_client client = + `Client (try List.assoc (Ipaddr.V4 client#other_ip) clients with Not_found -> `Unknown) + +let resolve_host = function + | `Client c -> resolve_client c + | `External ip -> `External (try List.assoc ip externals with Not_found -> `Unknown) + | (`Client_gateway | `Firewall_uplink | `NetVM) as x -> x + +let classify ~src ~dst packet = + let `IPv4 (_ip, transport) = packet in let proto = match transport with | `TCP ({Tcp.Tcp_packet.src_port; dst_port; _}, _) -> `TCP {sport = src_port; dport = dst_port} @@ -58,8 +71,8 @@ let classify t packet = in Some { packet; - src = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.src); - dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst); + src; + dst; proto; } @@ -80,7 +93,10 @@ let pp_proto fmt = function | `ICMP -> Format.pp_print_string fmt "ICMP" | `Unknown -> Format.pp_print_string fmt "UnknownProtocol" -let pp_packet fmt {src; dst; proto; packet = _} = +let pp_packet t fmt {src = _; dst = _; proto; packet} = + let `IPv4 (ip, _transport) = packet in + let src = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.src) in + let dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst) in Format.fprintf fmt "[src=%a dst=%a proto=%a]" pp_host src pp_host dst @@ -125,30 +141,18 @@ let nat_to t ~host ~port packet = (* Handle incoming packets *) -let parse_ips ips = List.map (fun (ip_str, id) -> (Ipaddr.of_string_exn ip_str, id)) ips - -let clients = parse_ips Rules.clients -let externals = parse_ips Rules.externals - -let resolve_host = function - | `Client c -> `Client (try List.assoc (Ipaddr.V4 c#other_ip) clients with Not_found -> `Unknown) - | `External ip -> `External (try List.assoc ip externals with Not_found -> `Unknown) - | (`Client_gateway | `Firewall_uplink | `NetVM) as x -> x - -let apply_rules t rules info = +let apply_rules t rules ~dst info = let packet = info.packet in - let resolved_info = { info with src = resolve_host info.src; - dst = resolve_host info.dst } in - match rules resolved_info, info.dst with + match rules info, dst with | `Accept, `Client client_link -> transmit_ipv4 packet client_link | `Accept, (`External _ | `NetVM) -> transmit_ipv4 packet t.Router.uplink | `Accept, (`Firewall_uplink | `Client_gateway) -> - Log.warn (fun f -> f "Bad rule: firewall can't accept packets %a" pp_packet info); + Log.warn (fun f -> f "Bad rule: firewall can't accept packets %a" (pp_packet t) info); return () | `NAT, _ -> add_nat_and_forward_ipv4 t packet | `NAT_to (host, port), _ -> nat_to t packet ~host ~port | `Drop reason, _ -> - Log.info (fun f -> f "Dropped packet (%s) %a" reason pp_packet info); + Log.info (fun f -> f "Dropped packet (%s) %a" reason (pp_packet t) info); return () let handle_low_memory t = @@ -159,7 +163,7 @@ let handle_low_memory t = `Memory_critical | `Ok -> Lwt.return `Ok -let ipv4_from_client t packet = +let ipv4_from_client t ~src packet = handle_low_memory t >>= function | `Memory_critical -> return () | `Ok -> @@ -168,23 +172,28 @@ let ipv4_from_client t packet = | Some frame -> forward_ipv4 t frame (* Some existing connection or redirect *) | None -> (* No existing NAT entry. Check the firewall rules. *) - match classify t packet with + let `IPv4 (ip, _transport) = packet in + let dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst) in + match classify ~src:(resolve_client src) ~dst:(resolve_host dst) packet with | None -> return () - | Some info -> apply_rules t Rules.from_client info + | Some info -> apply_rules t Rules.from_client ~dst info let ipv4_from_netvm t packet = handle_low_memory t >>= function | `Memory_critical -> return () | `Ok -> - match classify t packet with + let `IPv4 (ip, _transport) = packet in + let src = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.src) in + let dst = Router.classify t (Ipaddr.V4 ip.Ipv4_packet.dst) in + match classify ~src ~dst:(resolve_host dst) packet with | None -> return () | Some info -> - match info.src with + match src with | `Client _ | `Firewall_uplink | `Client_gateway -> - Log.warn (fun f -> f "Frame from NetVM has internal source IP address! %a" pp_packet info); + Log.warn (fun f -> f "Frame from NetVM has internal source IP address! %a" (pp_packet t) info); return () - | `External _ | `NetVM -> + | `External _ | `NetVM as src -> translate t packet >>= function | Some frame -> forward_ipv4 t frame | None -> - apply_rules t Rules.from_netvm info + apply_rules t Rules.from_netvm ~dst { info with src } diff --git a/firewall.mli b/firewall.mli index 3909ee0..9900f56 100644 --- a/firewall.mli +++ b/firewall.mli @@ -6,6 +6,6 @@ val ipv4_from_netvm : Router.t -> Nat_packet.t -> unit Lwt.t (** Handle a packet from the outside world (this module will validate the source IP). *) -val ipv4_from_client : Router.t -> Nat_packet.t -> unit Lwt.t +val ipv4_from_client : Router.t -> src:Fw_utils.client_link -> Nat_packet.t -> unit Lwt.t (** Handle a packet from a client. Caller must check the source IP matches the client's before calling this. *) diff --git a/packet.ml b/packet.ml index 97f1feb..d9b49bb 100644 --- a/packet.ml +++ b/packet.ml @@ -13,11 +13,10 @@ type ports = { type host = [ `Client of client_link | `Client_gateway | `Firewall_uplink | `NetVM | `External of Ipaddr.t ] -(* Note: 'a is either [host], or the result of applying [Rules.clients] and [Rules.externals] to a host. *) -type 'a info = { +type ('src, 'dst) info = { packet : Nat_packet.t; - src : 'a; - dst : 'a; + src : 'src; + dst : 'dst; proto : [ `UDP of ports | `TCP of ports | `ICMP | `Unknown ]; } diff --git a/rules.ml b/rules.ml index 352c98b..f8f253d 100644 --- a/rules.ml +++ b/rules.ml @@ -1,12 +1,9 @@ (* Copyright (C) 2015, Thomas Leonard See the README file for details. *) -(** Put your firewall rules here. *) +(** Put your firewall rules in this file. *) -open Packet - -(* OCaml normally warns if you don't match all fields, but that's OK here. *) -[@@@ocaml.warning "-9"] +open Packet (* Allow us to use definitions in packet.ml *) (* List your AppVM IP addresses here if you want to match on them in the rules below. Any client not listed here will appear as [`Client `Unknown]. *) @@ -25,11 +22,29 @@ let externals = [ *) ] -(** Decide what to do with a packet from a client VM. +(* OCaml normally warns if you don't match all fields, but that's OK here. *) +[@@@ocaml.warning "-9"] + +(** This function decides what to do with a packet from a client VM. + + It takes as input an argument [info] (of type [Packet.info]) describing the + packet, and returns an action (of type [Packet.action]) to perform. + + See packet.ml for the definitions of [info] and [action]. + Note: If the packet matched an existing NAT rule then this isn't called. *) -let from_client (info : _ info) : action = +let from_client (info : ([`Client of _], _) Packet.info) : Packet.action = match info with - (* Examples (add your own rules here): *) + (* Examples (add your own rules here): + + 1. Allows Dev to send SSH packets to Untrusted. + Note: responses are not covered by this! + 2. Allows clients to continue existing TCP connections with other clients. + This allows responses to SSH packets from the previous rule. + 3. Blocks an external site. + + In all cases, make sure you've added the VM name to [clients] or [externals] above, or it won't + match anything! *) (* | { src = `Client `Dev; dst = `Client `Untrusted; proto = `TCP { dport = 22 } } -> `Accept | { src = `Client _; dst = `Client _; proto = `TCP _; packet } @@ -43,6 +58,6 @@ let from_client (info : _ info) : action = (** Decide what to do with a packet received from the outside world. Note: If the packet matched an existing NAT rule then this isn't called. *) -let from_netvm (info : _ info) : action = +let from_netvm (info : ([`NetVM | `External of _], _) Packet.info) : Packet.action = match info with | _ -> `Drop "drop by default" From eec1e985e5ed1209979d799ad9ffe4b125f602ed Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Fri, 3 May 2019 10:45:15 +0100 Subject: [PATCH 4/6] Add overview of the main components of the firewall --- .gitignore | 2 +- README.md | 23 +++++++ diagrams/Makefile | 6 ++ diagrams/components.svg | 149 ++++++++++++++++++++++++++++++++++++++++ diagrams/components.txt | 20 ++++++ 5 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 diagrams/Makefile create mode 100644 diagrams/components.svg create mode 100644 diagrams/components.txt diff --git a/.gitignore b/.gitignore index f5cd959..bd2f111 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -Makefile +/Makefile _build/ log key_gen.ml diff --git a/README.md b/README.md index bfbef5f..960e568 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,29 @@ qvm-prefs --set my-app-vm netvm mirage-firewall Alternatively, you can configure `mirage-firewall` to be your default firewall VM. +### Components + +This diagram show the main components (each box corresponds to a source `.ml` file with the same name): + +

+ +

+ +Ethernet frames arrives from client qubes (such as `work` or `personal`) or from `sys-net`. +Internet (IP) packets are sent to `firewall`, which consults `rules` to decide what to do with the packet. +If it should be sent on, it uses `router` to send it to the chosen destination. +`client_net` watches the XenStore database provided by dom0 +to find out when clients need to be added or removed. + +The boot process: + +- `config.ml` describes the libraries used and static configuration settings (NAT table size). + The `mirage` tool uses this to generate `main.ml`. +- `main.ml` initialises the drivers selected by `config.ml` + and calls the `start` function in `unikernel.ml`. +- `unikernel.ml` connects the Qubes agents, sets up the networking components, + and then waits for a shutdown request. + ### Easy deployment for developers For development, use the [test-mirage][] scripts to deploy the unikernel (`qubes_firewall.xen`) from your development AppVM. diff --git a/diagrams/Makefile b/diagrams/Makefile new file mode 100644 index 0000000..a6fbc5f --- /dev/null +++ b/diagrams/Makefile @@ -0,0 +1,6 @@ +# Requires https://github.com/blampe/goat + +all: components.svg + +%.svg: %.txt + goat $^ > $@ diff --git a/diagrams/components.svg b/diagrams/components.svg new file mode 100644 index 0000000..1e996b1 --- /dev/null +++ b/diagrams/components.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +l +y +s +t +k +r +u +l +n +_ +r +i +e +l +o +n +k +n +o +o +e +e +e +l +s +t +( +f +p +i +i +o +w +t +u +n +- +a +o +X +S +r +m +u +c +r +] +e +r +i +n +s +t +e +k +s +w +e +. +n +e +l +r +s +e +s +r +l +[ +. +p +n +t +o +o +c +h +. +c +t +m +a +e +r +d +0 +) + + diff --git a/diagrams/components.txt b/diagrams/components.txt new file mode 100644 index 0000000..62e4f9e --- /dev/null +++ b/diagrams/components.txt @@ -0,0 +1,20 @@ + +----------+ + | rules | + +----------+ + ^ + |checks + | + +------------+ +-----+----+ + work <---->| +---->| firewall |<--------. + | | +-----+----+ | + | | | +----+---+ + [...] <---->| client_net | | | uplink |<----> sys-net + | | v +--------+ + | | +----------+ ^ +personal <---->| |<----+ router +---------' + +------+-----+ +----------+ + | + |monitors + v + XenStore + (dom0) From e15fc8c219d2b38aa4b16e9eb2e6224455355903 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Fri, 3 May 2019 11:12:58 +0100 Subject: [PATCH 5/6] Make example rule more restrictive In the (commented-out) example rules, instead of allowing any client to continue a TCP flow with any other client, just allow Untrusted to reply to Dev. This is all that is needed to make the SSH example work. --- rules.ml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rules.ml b/rules.ml index f8f253d..3959d14 100644 --- a/rules.ml +++ b/rules.ml @@ -39,15 +39,14 @@ let from_client (info : ([`Client of _], _) Packet.info) : Packet.action = 1. Allows Dev to send SSH packets to Untrusted. Note: responses are not covered by this! - 2. Allows clients to continue existing TCP connections with other clients. - This allows responses to SSH packets from the previous rule. + 2. Allows Untrusted to reply to Dev. 3. Blocks an external site. In all cases, make sure you've added the VM name to [clients] or [externals] above, or it won't match anything! *) (* | { src = `Client `Dev; dst = `Client `Untrusted; proto = `TCP { dport = 22 } } -> `Accept - | { src = `Client _; dst = `Client _; proto = `TCP _; packet } + | { src = `Client `Untrusted; dst = `Client `Dev; proto = `TCP _; packet } when not (is_tcp_start packet) -> `Accept | { dst = `External `GoogleDNS } -> `Drop "block Google DNS" *) From 691c4ae745c80d24132c0c2d67c39db66fafb26f Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Mon, 6 May 2019 10:37:24 +0100 Subject: [PATCH 6/6] Update build hash --- build-with-docker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-with-docker.sh b/build-with-docker.sh index d14c057..7345ca5 100755 --- a/build-with-docker.sh +++ b/build-with-docker.sh @@ -5,5 +5,5 @@ docker build -t qubes-mirage-firewall . echo Building Firewall... docker run --rm -i -v `pwd`:/home/opam/qubes-mirage-firewall qubes-mirage-firewall echo "SHA2 of build: $(sha256sum qubes_firewall.xen)" -echo "SHA2 last known: dbf7460fa628bea5d132a96fe7ba2cd832e3d9da7005ae74f6a124957f4848ea" +echo "SHA2 last known: 888cfd66e54c14da75be2bc4272efdb74c2ec8f9f144979f508a09410121482e" echo "(hashes should match for released versions)"