Skip to content

Commit e64b4bc

Browse files
authored
IPv6 IPs in host certificates for dual-stack management interfaces (#6419)
Adds IPv6 addresses to host certificates when their management interfaces are configured to use Dual stack. I've tested manually on IPv4-only hosts, and the certificates are generated in the same way as before. I've run the smoke and verification tests (Suite Run 215863), all of them passed @last-genius you probably want to test these changes, run `openssl x509 -text -in /etc/xensource/xapi-ssl.pem` on a dual host, and see that it contains the IPv6 and IPv4 addresses.
2 parents f9a5a8e + d94a4bf commit e64b4bc

File tree

5 files changed

+114
-53
lines changed

5 files changed

+114
-53
lines changed

ocaml/gencert/gencert.ml

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,20 @@ let main ~dbg ~path ~cert_gid ~sni () =
4747
init_inventory () ;
4848
let generator path =
4949
match sni with
50-
| SNI.Default ->
51-
let name, ip =
52-
match Networking_info.get_management_ip_addr ~dbg with
53-
| None ->
54-
D.error "gencert.ml: cannot get management ip address!" ;
55-
exit 1
56-
| Some x ->
57-
x
58-
in
59-
let dns_names = Networking_info.dns_names () in
60-
let ips = [ip] in
61-
let (_ : X509.Certificate.t) =
62-
Gencertlib.Selfcert.host ~name ~dns_names ~ips ~valid_for_days path
63-
cert_gid
64-
in
65-
()
50+
| SNI.Default -> (
51+
match Networking_info.get_host_certificate_subjects ~dbg with
52+
| Error cause ->
53+
let msg = Networking_info.management_ip_error_to_string cause in
54+
D.error
55+
"gencert.ml: failed to generate certificate subjects because %s" msg ;
56+
exit 1
57+
| Ok (name, dns_names, ips) ->
58+
let _ : X509.Certificate.t =
59+
Gencertlib.Selfcert.host ~name ~dns_names ~ips ~valid_for_days path
60+
cert_gid
61+
in
62+
()
63+
)
6664
| SNI.Xapi_pool ->
6765
let uuid = Inventory.lookup Inventory._installation_uuid in
6866
let (_ : X509.Certificate.t) =

ocaml/networkd/bin/network_server.ml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,12 +412,23 @@ module Interface = struct
412412
Ip.set_ipv6_link_local_addr name
413413
)
414414
| DHCP6 ->
415+
let gateway =
416+
Option.fold ~none:[]
417+
~some:(fun n -> [`gateway n])
418+
!config.gateway_interface
419+
in
420+
let dns =
421+
Option.fold ~none:[]
422+
~some:(fun n -> [`dns n])
423+
!config.dns_interface
424+
in
415425
if Dhclient.is_running ~ipv6:true name then
416426
ignore (Dhclient.stop ~ipv6:true name) ;
417427
Sysctl.set_ipv6_autoconf name false ;
418428
Ip.flush_ip_addr ~ipv6:true name ;
419429
Ip.set_ipv6_link_local_addr name ;
420-
ignore (Dhclient.ensure_running ~ipv6:true name [])
430+
let options = gateway @ dns in
431+
ignore (Dhclient.ensure_running ~ipv6:true name options)
421432
| Autoconf6 ->
422433
if Dhclient.is_running ~ipv6:true name then
423434
ignore (Dhclient.stop ~ipv6:true name) ;

ocaml/xapi-aux/networking_info.ml

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,22 @@ module L = Debug.Make (struct let name = __MODULE__ end)
1717

1818
let get_hostname () = try Unix.gethostname () with _ -> ""
1919

20-
exception Unexpected_address_type of string
20+
type management_ip_error =
21+
| Interface_missing
22+
| Unexpected_address_type of string
23+
| IP_missing
24+
| Other of exn
25+
26+
let management_ip_error_to_string = function
27+
| Interface_missing ->
28+
"Management interface is missing"
29+
| IP_missing ->
30+
"Management IP is missing"
31+
| Unexpected_address_type s ->
32+
Printf.sprintf
33+
"Unexpected address type. Expected 'ipv4' or 'ipv6', got %s" s
34+
| Other e ->
35+
Printexc.to_string e
2136

2237
(* Try to get all FQDNs, avoid localhost *)
2338
let dns_names () =
@@ -46,32 +61,63 @@ let ipaddr_to_cstruct = function
4661
| Ipaddr.V6 addr ->
4762
Cstruct.of_string (Ipaddr.V6.to_octets addr)
4863

49-
let list_head lst = List.nth_opt lst 0
50-
51-
let get_management_ip_addr ~dbg =
64+
let get_management_ip_addrs ~dbg =
5265
let iface = Inventory.lookup Inventory._management_interface in
5366
try
5467
if iface = "" || (not @@ Net.Interface.exists dbg iface) then
55-
None
68+
Error Interface_missing
5669
else
57-
let addrs =
70+
let ( let* ) = Result.bind in
71+
let* addrs =
72+
let ipv4 = Net.Interface.get_ipv4_addr dbg iface in
73+
let ipv6 = Net.Interface.get_ipv6_addr dbg iface in
5874
match
5975
String.lowercase_ascii
6076
(Inventory.lookup Inventory._management_address_type ~default:"ipv4")
6177
with
6278
| "ipv4" ->
63-
Net.Interface.get_ipv4_addr dbg iface
79+
Ok (ipv4, ipv6)
6480
| "ipv6" ->
65-
Net.Interface.get_ipv6_addr dbg iface
81+
Ok (ipv6, ipv4)
6682
| s ->
67-
let msg = Printf.sprintf "Expected 'ipv4' or 'ipv6', got %s" s in
68-
L.error "%s: %s" __FUNCTION__ msg ;
69-
raise (Unexpected_address_type msg)
83+
Error (Unexpected_address_type s)
7084
in
71-
addrs
72-
|> List.map (fun (addr, _) -> Ipaddr_unix.of_inet_addr addr)
7385
(* Filter out link-local addresses *)
74-
|> List.filter (fun addr -> Ipaddr.scope addr <> Ipaddr.Link)
75-
|> List.map (fun ip -> (Ipaddr.to_string ip, ipaddr_to_cstruct ip))
76-
|> list_head
77-
with _ -> None
86+
let no_local (addr, _) =
87+
let addr = Ipaddr_unix.of_inet_addr addr in
88+
if Ipaddr.scope addr <> Ipaddr.Link then
89+
Some addr
90+
else
91+
None
92+
in
93+
Ok
94+
( List.filter_map no_local (fst addrs)
95+
, List.filter_map no_local (snd addrs)
96+
)
97+
with e -> Error (Other e)
98+
99+
let get_management_ip_addr ~dbg =
100+
match get_management_ip_addrs ~dbg with
101+
| Ok (preferred, _) ->
102+
List.nth_opt preferred 0
103+
|> Option.map (fun addr -> (Ipaddr.to_string addr, ipaddr_to_cstruct addr))
104+
| Error _ ->
105+
None
106+
107+
let get_host_certificate_subjects ~dbg =
108+
let ( let* ) = Result.bind in
109+
let* ips, preferred_ip =
110+
match get_management_ip_addrs ~dbg with
111+
| Error e ->
112+
Error e
113+
| Ok (preferred, others) ->
114+
let ips = List.(rev_append (rev preferred) others) in
115+
Option.fold ~none:(Error IP_missing)
116+
~some:(fun ip -> Ok (List.map ipaddr_to_cstruct ips, ip))
117+
(List.nth_opt ips 0)
118+
in
119+
let dns_names = dns_names () in
120+
let name =
121+
match dns_names with [] -> Ipaddr.to_string preferred_ip | dns :: _ -> dns
122+
in
123+
Ok (name, dns_names, ips)

ocaml/xapi-aux/networking_info.mli

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,23 @@ val get_hostname : unit -> string
1414
(** [get_hostname ()] returns the hostname as returned by Unix.gethostname.
1515
If there is an error "" is returned. *)
1616

17-
exception Unexpected_address_type of string
17+
type management_ip_error =
18+
| Interface_missing
19+
| Unexpected_address_type of string
20+
| IP_missing
21+
| Other of exn
1822

19-
val dns_names : unit -> string list
20-
(** [dns_names ()] returns a list of the hostnames that the host may have.
21-
Ignores empty names as well as "localhost" *)
23+
val management_ip_error_to_string : management_ip_error -> string
24+
(** [management_ip_error err] returns a string representation of [err], useful
25+
only for logging. *)
2226

2327
val get_management_ip_addr : dbg:string -> (string * Cstruct.t) option
24-
(** [get_management_ip_addr ~dbg] returns the IP of the management network.
25-
If the system does not have management address None is return.
26-
[Unexpected_address_type] is raised if there is an unexpected address is
27-
stored. The address is return in two formats: human-readable string and
28-
its bytes representation. *)
28+
(** [get_management_ip_addr ~dbg] returns the preferred IP of the management
29+
network, or None. The address is returned in two formats: a human-readable
30+
string and its bytes representation. *)
31+
32+
val get_host_certificate_subjects :
33+
dbg:string
34+
-> (string * string list * Cstruct.t list, management_ip_error) Result.t
35+
(** [get_host_certificate_subjects ~dbg] returns the main, dns names and ip
36+
addresses that identify the host in secure connections. *)

ocaml/xapi/xapi_host.ml

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,19 +1583,17 @@ let install_server_certificate ~__context ~host ~certificate ~private_key
15831583
replace_host_certificate ~__context ~type':`host ~host write_cert_fs
15841584

15851585
let _new_host_cert ~dbg ~path : X509.Certificate.t =
1586-
let ip_as_string, ip =
1587-
match Networking_info.get_management_ip_addr ~dbg with
1588-
| None ->
1586+
let name, dns_names, ips =
1587+
match Networking_info.get_host_certificate_subjects ~dbg with
1588+
| Error cause ->
1589+
let msg = Networking_info.management_ip_error_to_string cause in
15891590
Helpers.internal_error ~log_err:true ~err_fun:D.error
1590-
"%s: failed to get management IP" __LOC__
1591-
| Some ip ->
1592-
ip
1591+
"%s: failed to generate certificate subjects because %s" __LOC__ msg
1592+
| Ok (name, dns_names, ips) ->
1593+
(name, dns_names, ips)
15931594
in
1594-
let dns_names = Networking_info.dns_names () in
1595-
let cn = match dns_names with [] -> ip_as_string | dns :: _ -> dns in
1596-
let ips = [ip] in
15971595
let valid_for_days = !Xapi_globs.cert_expiration_days in
1598-
Gencertlib.Selfcert.host ~name:cn ~dns_names ~ips ~valid_for_days path
1596+
Gencertlib.Selfcert.host ~name ~dns_names ~ips ~valid_for_days path
15991597
!Xapi_globs.server_cert_group_id
16001598

16011599
let reset_server_certificate ~__context ~host =

0 commit comments

Comments
 (0)