Skip to content

CA-408550: XSI-1834: Host netbios name should be added to local hosts file to avoid DNS lookup #6386

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 1, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions ocaml/tests/test_extauth_plugin_ADwinbind.ml
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,101 @@ let test_wbinfo_exception_of_stderr =
in
matrix |> List.map @@ fun (inp, exp) -> ("<omit inp>", `Quick, check inp exp)

let test_add_ipv4_localhost_to_hosts =
let open Extauth_plugin_ADwinbind in
let check inp exp () =
let msg =
Printf.sprintf "%s -> %s" (String.concat "\n" inp) (String.concat "\n" exp)
in
let actual = HostsConfIPv4.join "hostname" "domain" inp in
Alcotest.(check @@ list string) msg exp actual
in
let matrix =
[
( [
"127.0.0.1 localhost localhost.localdomain localhost4 \
localhost4.localdomain4"
]
, [
"127.0.0.1 localhost localhost.localdomain localhost4 \
localhost4.localdomain4 hostname hostname.domain"
]
)
; ( ["127.0.0.1 localhost hostname hostname.domain localhost.localdomain"]
, ["127.0.0.1 localhost localhost.localdomain hostname hostname.domain"]
)
; ( ["192.168.0.1 some_host"]
, ["127.0.0.1 hostname hostname.domain"; "192.168.0.1 some_host"]
)
; ([], ["127.0.0.1 hostname hostname.domain"])
]
in
matrix |> List.map @@ fun (inp, exp) -> ("<omit inp>", `Quick, check inp exp)

let test_add_ipv4_and_ipv6_localhost_to_hosts =
let open Extauth_plugin_ADwinbind in
let check inp exp () =
let msg =
Printf.sprintf "%s -> %s" (String.concat "\n" inp) (String.concat "\n" exp)
in
let actual =
HostsConfIPv6.join "hostname" "domain" inp |> fun lines ->
HostsConfIPv4.join ~name:"hostname" ~domain:"domain" ~lines
in
Alcotest.(check @@ list string) msg exp actual
in
let matrix =
[
( ["127.0.0.1 localhost"]
, [
"::1 hostname hostname.domain"
; "127.0.0.1 localhost hostname hostname.domain"
]
)
; ( ["127.0.0.1 localhost"; "::1 localhost"]
, [
"127.0.0.1 localhost hostname hostname.domain"
; "::1 localhost hostname hostname.domain"
]
)
; ( []
, ["127.0.0.1 hostname hostname.domain"; "::1 hostname hostname.domain"]
)
]
in
matrix |> List.map @@ fun (inp, exp) -> ("<omit inp>", `Quick, check inp exp)

let test_remove_ipv4_localhost_from_hosts =
let open Extauth_plugin_ADwinbind in
let check inp exp () =
let msg =
Printf.sprintf "%s -> %s" (String.concat "\n" inp) (String.concat "\n" exp)
in
let actual = HostsConfIPv4.leave "hostname" "domain" inp in
Alcotest.(check @@ list string) msg exp actual
in
let matrix =
[
( [
"127.0.0.1 localhost localhost.localdomain localhost4 \
localhost4.localdomain4"
]
, [
"127.0.0.1 localhost localhost.localdomain localhost4 \
localhost4.localdomain4"
]
)
; ( ["127.0.0.1 localhost hostname hostname.domain localhost.localdomain"]
, ["127.0.0.1 localhost localhost.localdomain"]
)
; (["127.0.0.1 hostname hostname.domain"], [])
; ( ["192.168.0.1 some_host"; "127.0.0.1 localhost hostname"]
, ["192.168.0.1 some_host"; "127.0.0.1 localhost"]
)
]
in
matrix |> List.map @@ fun (inp, exp) -> ("<omit inp>", `Quick, check inp exp)

let tests =
[
("ADwinbind:extract_ou_config", ExtractOuConfig.tests)
Expand All @@ -512,4 +607,13 @@ let tests =
; ( "ADwinbind:test_wbinfo_exception_of_stderr"
, test_wbinfo_exception_of_stderr
)
; ( "ADwinbind:test_add_ipv4_localhost_to_hosts"
, test_add_ipv4_localhost_to_hosts
)
; ( "ADwinbind:test_remove_ipv4_localhost_from_hosts"
, test_remove_ipv4_localhost_from_hosts
)
; ( "ADwinbind:test_add_ipv4_and_ipv6_localhost_to_hosts"
, test_add_ipv4_and_ipv6_localhost_to_hosts
)
]
204 changes: 142 additions & 62 deletions ocaml/xapi/extauth_plugin_ADwinbind.ml
Original file line number Diff line number Diff line change
Expand Up @@ -828,48 +828,39 @@ let config_winbind_daemon ~workgroup ~netbios_name ~domain =
let conf_contents =
match (workgroup, netbios_name, domain) with
| Some wkgroup, Some netbios, Some dom ->
String.concat "\n"
[
"# auto-generated by xapi"
; "[global]"
; "kerberos method = secrets and keytab"
; Printf.sprintf "realm = %s" dom
; "security = ADS"
; "template shell = /bin/bash"
; "winbind refresh tickets = yes"
; "winbind enum groups = no"
; "winbind enum users = no"
; "winbind scan trusted domains = yes"
; "winbind use krb5 enterprise principals = yes"
; Printf.sprintf "winbind cache time = %d"
!Xapi_globs.winbind_cache_time
; Printf.sprintf "machine password timeout = 0"
; Printf.sprintf "kerberos encryption types = %s"
(Kerberos_encryption_types.Winbind.to_string
!Xapi_globs.winbind_kerberos_encryption_type
)
; Printf.sprintf "workgroup = %s" wkgroup
; Printf.sprintf "netbios name = %s" netbios
; "idmap config * : range = 3000000-3999999"
; Printf.sprintf "idmap config %s: backend = rid" dom
; Printf.sprintf "idmap config %s: range = 2000000-2999999" dom
; Printf.sprintf "log level = %s" (debug_level ())
; Printf.sprintf "allow kerberos auth fallback = %s" allow_fallback
; "idmap config * : backend = tdb"
; "" (* Empty line at the end *)
]
[
"# autogenerated by xapi"
; "[global]"
; "kerberos method = secrets and keytab"
; Printf.sprintf "realm = %s" dom
; "security = ADS"
; "template shell = /bin/bash"
; "winbind refresh tickets = yes"
; "winbind enum groups = no"
; "winbind enum users = no"
; "winbind scan trusted domains = yes"
; "winbind use krb5 enterprise principals = yes"
; Printf.sprintf "winbind cache time = %d"
!Xapi_globs.winbind_cache_time
; Printf.sprintf "machine password timeout = 0"
; Printf.sprintf "kerberos encryption types = %s"
(Kerberos_encryption_types.Winbind.to_string
!Xapi_globs.winbind_kerberos_encryption_type
)
; Printf.sprintf "workgroup = %s" wkgroup
; Printf.sprintf "netbios name = %s" netbios
; "idmap config * : range = 3000000-3999999"
; Printf.sprintf "idmap config %s: backend = rid" dom
; Printf.sprintf "idmap config %s: range = 2000000-2999999" dom
; Printf.sprintf "log level = %s" (debug_level ())
; Printf.sprintf "allow kerberos auth fallback = %s" allow_fallback
; "idmap config * : backend = tdb"
; "" (* Empty line at the end *)
]
| _ ->
String.concat "\n"
[
"# autogenerated by xapi"; "[global]"; "" (* Empty line at the end *)
]
["# autogenerated by xapi"; "[global]"; "" (* Empty line at the end *)]
in

let len = String.length conf_contents in
Unixext.atomic_write_to_file smb_config 0o0644 (fun fd ->
let (_ : int) = Unix.single_write_substring fd conf_contents 0 len in
Unix.fsync fd
)
Helpers.ListFile.to_path smb_config conf_contents

let clear_winbind_config () =
(* Keep the winbind configuration if xapi config file specified explictly,
Expand Down Expand Up @@ -1222,27 +1213,21 @@ module RotateMachinePassword = struct
in

let conf_contents =
String.concat "\n"
([
"# auto-generated by xapi"
; "[libdefaults]"
; Printf.sprintf "default_realm = %s" realm
; "[realms]"
; Printf.sprintf "%s={" realm
; Printf.sprintf "kpasswd_server=%s" kdc_fqdn
; Printf.sprintf "kdc=%s" kdc_fqdn
; "}" (* include winbind generated configure if exists *)
]
@ include_item
@ [""] (* Empty line at the end *)
)
[
"# autogenerated by xapi"
; "[libdefaults]"
; Printf.sprintf "default_realm = %s" realm
; "[realms]"
; Printf.sprintf "%s={" realm
; Printf.sprintf "kpasswd_server=%s" kdc_fqdn
; Printf.sprintf "kdc=%s" kdc_fqdn
; "}" (* include winbind generated configure if exists *)
]
@ include_item
@ [""]
(* Empty line at the end *)
in

let len = String.length conf_contents in
Unixext.atomic_write_to_file tmp_krb5_conf 0o0644 (fun fd ->
let (_ : int) = Unix.single_write_substring fd conf_contents 0 len in
Unix.fsync fd
)
Helpers.ListFile.to_path tmp_krb5_conf conf_contents

let clear_tmp_krb5_conf () =
if !Xapi_globs.winbind_keep_configuration then
Expand Down Expand Up @@ -1307,6 +1292,83 @@ module RotateMachinePassword = struct
let stop_rotate () = Scheduler.remove_from_queue task_name
end

module type LocalHostTag = sig
val local_ip : string
end

module HostsConfTagIPv4 : LocalHostTag = struct let local_ip = "127.0.0.1" end

module HostsConfTagIPv6 : LocalHostTag = struct let local_ip = "::1" end

module type HostsConf = sig
(* add the domain info into conf*)
val join : name:string -> domain:string -> lines:string list -> string list

(* remove the domain info from conf*)
val leave : name:string -> domain:string -> lines:string list -> string list
end

module HostsConfFunc (T : LocalHostTag) : HostsConf = struct
let sep = ' '

let sep_str = String.make 1 sep

type t = Add | Remove

let interest line = String.starts_with ~prefix:T.local_ip line

let handle op name domain line =
let line = String.lowercase_ascii line in
let name = String.lowercase_ascii name in
let domain = String.lowercase_ascii domain in
let fqdn = Printf.sprintf "%s.%s" name domain in
match interest line with
| false ->
line
| true ->
String.split_on_char sep line
|> List.filter (fun x -> x <> name && x <> fqdn)
|> (fun x -> match op with Add -> x @ [name; fqdn] | Remove -> x)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before we add something, should we make sure there is not previous entry for it? That is - should an addition always include a removal of any existing entry such that the new entry is the definitive one? Is that even possible?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Every entry should only exists once for an IP address
  • The order does not really matter
  • We remove the entry and add new one if necessary, to keep unify for Add and Remove operation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this still work when name or fqdn changes? So: the file contains an outdated entry; and the current name or fqdn does not match the previous one.

|> String.concat sep_str

let leave ~name ~domain ~lines =
List.map (fun line -> handle Remove name domain line) lines
(* If no name for local ip left, just remove it *)
|> List.filter (fun x -> String.trim x <> T.local_ip)

let join ~name ~domain ~lines =
List.map (fun line -> handle Add name domain line) lines |> fun x ->
match List.exists (fun l -> interest l) x with
| true ->
x
| false ->
(* Does not found and updated the conf, then add one *)
[
Printf.sprintf "%s%s%s%s%s.%s" T.local_ip sep_str name sep_str name
domain
]
@ x
end

module HostsConfIPv4 = HostsConfFunc (HostsConfTagIPv4)
module HostsConfIPv6 = HostsConfFunc (HostsConfTagIPv6)

module ConfigHosts = struct
let path = "/etc/hosts"

let join ~name ~domain =
Helpers.ListFile.of_path path
|> HostsConfIPv4.join ~name ~domain
|> HostsConfIPv6.join ~name ~domain
|> Helpers.ListFile.to_path path

let leave ~name ~domain =
Helpers.ListFile.of_path path
|> HostsConfIPv4.leave ~name ~domain
|> HostsConfIPv6.leave ~name ~domain
|> Helpers.ListFile.to_path path
end

let build_netbios_name ~config_params =
let key = "netbios-name" in
match List.assoc_opt key config_params with
Expand Down Expand Up @@ -1628,18 +1690,21 @@ module AuthADWinbind : Auth_signature.AUTH_MODULE = struct
~netbios_name:(Some netbios_name) ;
ClosestKdc.trigger_update ~start:0. ;
RotateMachinePassword.trigger_rotate ~start:0. ;
ConfigHosts.join ~domain:service_name ~name:netbios_name ;
(* Trigger right now *)
debug "Succeed to join domain %s" service_name
with
| Forkhelpers.Spawn_internal_error (_, stdout, _) ->
error "Join domain: %s error: %s" service_name stdout ;
clear_winbind_config () ;
ConfigHosts.leave ~domain:service_name ~name:netbios_name ;
(* The configure is kept for debug purpose with max level *)
raise (Auth_service_error (stdout |> tag_from_err_msg, stdout))
| Xapi_systemctl.Systemctl_fail _ ->
let msg = Printf.sprintf "Failed to start %s" Winbind.name in
error "Start daemon error: %s" msg ;
config_winbind_daemon ~domain:None ~workgroup:None ~netbios_name:None ;
ConfigHosts.leave ~domain:service_name ~name:netbios_name ;
raise (Auth_service_error (E_GENERIC, msg))
| e ->
let msg =
Expand All @@ -1650,6 +1715,7 @@ module AuthADWinbind : Auth_signature.AUTH_MODULE = struct
in
error "Enable extauth error: %s" msg ;
clear_winbind_config () ;
ConfigHosts.leave ~domain:service_name ~name:netbios_name ;
raise (Auth_service_error (E_GENERIC, msg))

(* unit on_disable()
Expand All @@ -1663,7 +1729,14 @@ module AuthADWinbind : Auth_signature.AUTH_MODULE = struct
let@ __context = Context.with_tracing ~__context __FUNCTION__ in
let user = List.assoc_opt "user" config_params in
let pass = List.assoc_opt "pass" config_params in
let {service_name; _} = get_domain_info_from_db () in
let {service_name; workgroup; netbios_name; _} =
get_domain_info_from_db ()
in
( if Option.is_some netbios_name then
Option.get netbios_name |> fun name ->
ConfigHosts.leave ~domain:service_name ~name
) ;

(* Clean extauth config *)
persist_extauth_config ~domain:None ~user:None ~ou_conf:[] ~workgroup:None
~machine_pwd_last_change_time:None ~netbios_name:None ;
Expand All @@ -1688,7 +1761,14 @@ module AuthADWinbind : Auth_signature.AUTH_MODULE = struct
Winbind.start ~timeout:5. ~wait_until_success:true ;
ClosestKdc.trigger_update ~start:ClosestKdc.startup_delay ;
RotateMachinePassword.trigger_rotate ~start:5. ;
Winbind.check_ready_to_serve ~timeout:300.
Winbind.check_ready_to_serve ~timeout:300. ;

let {service_name; workgroup; netbios_name; _} =
get_domain_info_from_db ()
in
if Option.is_some netbios_name then
Option.get netbios_name |> fun name ->
ConfigHosts.join ~domain:service_name ~name

(* unit on_xapi_exit()

Expand Down
12 changes: 12 additions & 0 deletions ocaml/xapi/extauth_plugin_ADwinbind.mli
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,15 @@ module Migrate_from_pbis : sig

val parse_value_from_pbis : string -> string
end

module type HostsConf = sig
(* add the domain info into conf*)
val join : name:string -> domain:string -> lines:string list -> string list

(* remove the domain info from conf*)
val leave : name:string -> domain:string -> lines:string list -> string list
end

module HostsConfIPv4 : HostsConf

module HostsConfIPv6 : HostsConf
Loading
Loading