Skip to content

Commit e29eda2

Browse files
authored
CP-53478: Implement SSH-related APIs for Dom0 SSH control (#6394)
Implemented XAPI APIs: - `set_ssh_enabled_timeout` - `set_console_idle_timeout` These APIs allow XAPI to configure timeouts for the SSH service and idle console sessions from both host and pool level. Updated `records.ml` to support `host-param-set/get/list` and `pool-param-set/get/list` for SSH-related fields.
2 parents 25ed999 + cb9277d commit e29eda2

File tree

6 files changed

+247
-18
lines changed

6 files changed

+247
-18
lines changed

ocaml/idl/datamodel_errors.ml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2040,6 +2040,12 @@ let _ =
20402040
error Api_errors.disable_ssh_partially_failed ["hosts"]
20412041
~doc:"Some of hosts failed to disable SSH access." () ;
20422042

2043+
error Api_errors.set_ssh_timeout_partially_failed ["hosts"]
2044+
~doc:"Some hosts failed to set SSH timeout." () ;
2045+
2046+
error Api_errors.set_console_timeout_partially_failed ["hosts"]
2047+
~doc:"Some hosts failed to set console timeout." () ;
2048+
20432049
error Api_errors.host_driver_no_hardware ["driver variant"]
20442050
~doc:"No hardware present for this host driver variant" () ;
20452051

ocaml/xapi-cli-server/records.ml

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ let nullref = Ref.string_of Ref.null
2020

2121
let nid = "<not in database>"
2222

23+
let inconsistent = "<inconsistent>"
24+
2325
let unknown_time = "<unknown time>"
2426

2527
let string_of_float f = Printf.sprintf "%.3f" f
@@ -204,6 +206,37 @@ let get_pbds_host rpc session_id pbds =
204206
let get_sr_host rpc session_id record =
205207
get_pbds_host rpc session_id record.API.sR_PBDs
206208

209+
(** Get consistent field from all hosts, or return a default value if the field
210+
is not the same on all hosts. *)
211+
let get_consistent_field_or_default ~rpc ~session_id ~getter ~transform ~default
212+
=
213+
match Client.Host.get_all ~rpc ~session_id with
214+
| [] ->
215+
default
216+
| hosts -> (
217+
let result =
218+
List.fold_left
219+
(fun acc host ->
220+
match acc with
221+
| `Inconsistent ->
222+
`Inconsistent
223+
| `NotSet ->
224+
`Value (getter ~rpc ~session_id ~self:host |> transform)
225+
| `Value v ->
226+
let current = getter ~rpc ~session_id ~self:host |> transform in
227+
if v = current then `Value v else `Inconsistent
228+
)
229+
`NotSet hosts
230+
in
231+
match result with
232+
| `Value v ->
233+
v
234+
| `Inconsistent ->
235+
default
236+
| `NotSet ->
237+
default
238+
)
239+
207240
let bond_record rpc session_id bond =
208241
let _ref = ref bond in
209242
let empty_record =
@@ -1506,6 +1539,42 @@ let pool_record rpc session_id pool =
15061539
)
15071540
~get_map:(fun () -> (x ()).API.pool_license_server)
15081541
()
1542+
; make_field ~name:"ssh-enabled"
1543+
~get:(fun () ->
1544+
get_consistent_field_or_default ~rpc ~session_id
1545+
~getter:Client.Host.get_ssh_enabled ~transform:string_of_bool
1546+
~default:inconsistent
1547+
)
1548+
()
1549+
; make_field ~name:"ssh-enabled-timeout"
1550+
~get:(fun () ->
1551+
get_consistent_field_or_default ~rpc ~session_id
1552+
~getter:Client.Host.get_ssh_enabled_timeout
1553+
~transform:Int64.to_string ~default:inconsistent
1554+
)
1555+
~set:(fun value ->
1556+
Client.Pool.set_ssh_enabled_timeout ~rpc ~session_id ~self:pool
1557+
~value:(safe_i64_of_string "ssh-enabled-timeout" value)
1558+
)
1559+
()
1560+
; make_field ~name:"ssh-expiry"
1561+
~get:(fun () ->
1562+
get_consistent_field_or_default ~rpc ~session_id
1563+
~getter:Client.Host.get_ssh_expiry ~transform:Date.to_rfc3339
1564+
~default:inconsistent
1565+
)
1566+
()
1567+
; make_field ~name:"console-idle-timeout"
1568+
~get:(fun () ->
1569+
get_consistent_field_or_default ~rpc ~session_id
1570+
~getter:Client.Host.get_console_idle_timeout
1571+
~transform:Int64.to_string ~default:inconsistent
1572+
)
1573+
~set:(fun value ->
1574+
Client.Pool.set_console_idle_timeout ~rpc ~session_id ~self:pool
1575+
~value:(safe_i64_of_string "console-idle-timeout" value)
1576+
)
1577+
()
15091578
]
15101579
}
15111580

@@ -3265,6 +3334,26 @@ let host_record rpc session_id host =
32653334
; make_field ~name:"last-update-hash"
32663335
~get:(fun () -> (x ()).API.host_last_update_hash)
32673336
()
3337+
; make_field ~name:"ssh-enabled"
3338+
~get:(fun () -> string_of_bool (x ()).API.host_ssh_enabled)
3339+
()
3340+
; make_field ~name:"ssh-enabled-timeout"
3341+
~get:(fun () -> Int64.to_string (x ()).API.host_ssh_enabled_timeout)
3342+
~set:(fun value ->
3343+
Client.Host.set_ssh_enabled_timeout ~rpc ~session_id ~self:host
3344+
~value:(safe_i64_of_string "ssh-enabled-timeout" value)
3345+
)
3346+
()
3347+
; make_field ~name:"ssh-expiry"
3348+
~get:(fun () -> Date.to_rfc3339 (x ()).API.host_ssh_expiry)
3349+
()
3350+
; make_field ~name:"console-idle-timeout"
3351+
~get:(fun () -> Int64.to_string (x ()).API.host_console_idle_timeout)
3352+
~set:(fun value ->
3353+
Client.Host.set_console_idle_timeout ~rpc ~session_id ~self:host
3354+
~value:(safe_i64_of_string "console-idle-timeout" value)
3355+
)
3356+
()
32683357
]
32693358
}
32703359

ocaml/xapi-consts/api_errors.ml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,6 +1420,12 @@ let enable_ssh_partially_failed = add_error "ENABLE_SSH_PARTIALLY_FAILED"
14201420

14211421
let disable_ssh_partially_failed = add_error "DISABLE_SSH_PARTIALLY_FAILED"
14221422

1423+
let set_ssh_timeout_partially_failed =
1424+
add_error "SET_SSH_TIMEOUT_PARTIALLY_FAILED"
1425+
1426+
let set_console_timeout_partially_failed =
1427+
add_error "SET_CONSOLE_TIMEOUT_PARTIALLY_FAILED"
1428+
14231429
let host_driver_no_hardware = add_error "HOST_DRIVER_NO_HARDWARE"
14241430

14251431
let tls_verification_not_enabled_in_pool =

ocaml/xapi/xapi_globs.ml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,12 @@ let gpumon_stop_timeout = ref 10.0
12871287

12881288
let reboot_required_hfxs = ref "/run/reboot-required.hfxs"
12891289

1290+
let console_timeout_profile_path = ref "/etc/profile.d/console_timeout.sh"
1291+
1292+
let job_for_disable_ssh = ref "Disable SSH"
1293+
1294+
let ssh_service = ref "sshd"
1295+
12901296
(* Fingerprint of default patch key *)
12911297
let citrix_patch_key =
12921298
"NERDNTUzMDMwRUMwNDFFNDI4N0M4OEVCRUFEMzlGOTJEOEE5REUyNg=="

ocaml/xapi/xapi_host.ml

Lines changed: 124 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3114,26 +3114,134 @@ let emergency_clear_mandatory_guidance ~__context =
31143114
) ;
31153115
Db.Host.set_pending_guidances ~__context ~self ~value:[]
31163116

3117+
let disable_ssh_internal ~__context ~self =
3118+
try
3119+
debug "Disabling SSH for host %s" (Helpers.get_localhost_uuid ()) ;
3120+
Xapi_systemctl.disable ~wait_until_success:false !Xapi_globs.ssh_service ;
3121+
Xapi_systemctl.stop ~wait_until_success:false !Xapi_globs.ssh_service ;
3122+
Db.Host.set_ssh_enabled ~__context ~self ~value:false
3123+
with e ->
3124+
error "Failed to disable SSH for host %s: %s" (Ref.string_of self)
3125+
(Printexc.to_string e) ;
3126+
Helpers.internal_error "Failed to disable SSH: %s" (Printexc.to_string e)
3127+
3128+
let schedule_disable_ssh_job ~__context ~self ~timeout =
3129+
let host_uuid = Helpers.get_localhost_uuid () in
3130+
let expiry_time =
3131+
match
3132+
Ptime.add_span (Ptime_clock.now ())
3133+
(Ptime.Span.of_int_s (Int64.to_int timeout))
3134+
with
3135+
| None ->
3136+
error "Invalid SSH timeout: %Ld" timeout ;
3137+
raise
3138+
(Api_errors.Server_error
3139+
( Api_errors.invalid_value
3140+
, ["ssh_enabled_timeout"; Int64.to_string timeout]
3141+
)
3142+
)
3143+
| Some t ->
3144+
Ptime.to_float_s t |> Date.of_unix_time
3145+
in
3146+
3147+
debug "Scheduling SSH disable job for host %s with timeout %Ld seconds"
3148+
host_uuid timeout ;
3149+
3150+
(* Remove any existing job first *)
3151+
Xapi_stdext_threads_scheduler.Scheduler.remove_from_queue
3152+
!Xapi_globs.job_for_disable_ssh ;
3153+
3154+
Xapi_stdext_threads_scheduler.Scheduler.add_to_queue
3155+
!Xapi_globs.job_for_disable_ssh
3156+
Xapi_stdext_threads_scheduler.Scheduler.OneShot (Int64.to_float timeout)
3157+
(fun () -> disable_ssh_internal ~__context ~self
3158+
) ;
3159+
3160+
Db.Host.set_ssh_expiry ~__context ~self ~value:expiry_time
3161+
31173162
let enable_ssh ~__context ~self =
31183163
try
3119-
Xapi_systemctl.enable ~wait_until_success:false "sshd" ;
3120-
Xapi_systemctl.start ~wait_until_success:false "sshd"
3121-
with _ ->
3122-
raise
3123-
(Api_errors.Server_error
3124-
(Api_errors.enable_ssh_failed, [Ref.string_of self])
3125-
)
3164+
debug "Enabling SSH for host %s" (Helpers.get_localhost_uuid ()) ;
3165+
3166+
Xapi_systemctl.enable ~wait_until_success:false !Xapi_globs.ssh_service ;
3167+
Xapi_systemctl.start ~wait_until_success:false !Xapi_globs.ssh_service ;
3168+
3169+
let timeout = Db.Host.get_ssh_enabled_timeout ~__context ~self in
3170+
( match timeout with
3171+
| 0L ->
3172+
Xapi_stdext_threads_scheduler.Scheduler.remove_from_queue
3173+
!Xapi_globs.job_for_disable_ssh
3174+
| t ->
3175+
schedule_disable_ssh_job ~__context ~self ~timeout:t
3176+
) ;
3177+
3178+
Db.Host.set_ssh_enabled ~__context ~self ~value:true
3179+
with e ->
3180+
error "Failed to enable SSH on host %s: %s" (Ref.string_of self)
3181+
(Printexc.to_string e) ;
3182+
Helpers.internal_error "Failed to enable SSH: %s" (Printexc.to_string e)
31263183

31273184
let disable_ssh ~__context ~self =
3185+
Xapi_stdext_threads_scheduler.Scheduler.remove_from_queue
3186+
!Xapi_globs.job_for_disable_ssh ;
3187+
disable_ssh_internal ~__context ~self ;
3188+
Db.Host.set_ssh_expiry ~__context ~self ~value:(Date.now ())
3189+
3190+
let set_ssh_enabled_timeout ~__context ~self ~value =
3191+
let validate_timeout value =
3192+
(* the max timeout is two days: 172800L = 2*24*60*60 *)
3193+
if value < 0L || value > 172800L then
3194+
raise
3195+
(Api_errors.Server_error
3196+
( Api_errors.invalid_value
3197+
, ["ssh_enabled_timeout"; Int64.to_string value]
3198+
)
3199+
)
3200+
in
3201+
validate_timeout value ;
3202+
debug "Setting SSH timeout for host %s to %Ld seconds"
3203+
(Db.Host.get_uuid ~__context ~self)
3204+
value ;
3205+
Db.Host.set_ssh_enabled_timeout ~__context ~self ~value ;
3206+
if Db.Host.get_ssh_enabled ~__context ~self then
3207+
match value with
3208+
| 0L ->
3209+
Xapi_stdext_threads_scheduler.Scheduler.remove_from_queue
3210+
!Xapi_globs.job_for_disable_ssh ;
3211+
Db.Host.set_ssh_expiry ~__context ~self ~value:Date.epoch
3212+
| t ->
3213+
schedule_disable_ssh_job ~__context ~self ~timeout:t
3214+
3215+
let set_console_idle_timeout ~__context ~self ~value =
3216+
let assert_timeout_valid timeout =
3217+
if timeout < 0L then
3218+
raise
3219+
(Api_errors.Server_error
3220+
( Api_errors.invalid_value
3221+
, ["console_timeout"; Int64.to_string timeout]
3222+
)
3223+
)
3224+
in
3225+
3226+
assert_timeout_valid value ;
31283227
try
3129-
Xapi_systemctl.disable ~wait_until_success:false "sshd" ;
3130-
Xapi_systemctl.stop ~wait_until_success:false "sshd"
3131-
with _ ->
3132-
raise
3133-
(Api_errors.Server_error
3134-
(Api_errors.disable_ssh_failed, [Ref.string_of self])
3135-
)
3228+
let content =
3229+
match value with
3230+
| 0L ->
3231+
"# Console timeout is disabled\n"
3232+
| timeout ->
3233+
Printf.sprintf "# Console timeout configuration\nexport TMOUT=%Ld\n"
3234+
timeout
3235+
in
31363236

3137-
let set_ssh_enabled_timeout ~__context ~self:_ ~value:_ = ()
3237+
Unixext.atomic_write_to_file !Xapi_globs.console_timeout_profile_path 0o0644
3238+
(fun fd ->
3239+
Unix.write fd (Bytes.of_string content) 0 (String.length content)
3240+
|> ignore
3241+
) ;
31383242

3139-
let set_console_idle_timeout ~__context ~self:_ ~value:_ = ()
3243+
Db.Host.set_console_idle_timeout ~__context ~self ~value
3244+
with e ->
3245+
error "Failed to configure console timeout: %s" (Printexc.to_string e) ;
3246+
Helpers.internal_error "Failed to set console timeout: %Ld: %s" value
3247+
(Printexc.to_string e)

ocaml/xapi/xapi_pool.ml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4056,12 +4056,26 @@ module Ssh = struct
40564056
let disable ~__context ~self:_ =
40574057
operate ~__context ~action:Client.Host.disable_ssh
40584058
~error:Api_errors.disable_ssh_partially_failed
4059+
4060+
let set_enabled_timeout ~__context ~self:_ ~value =
4061+
operate ~__context
4062+
~action:(fun ~rpc ~session_id ~self ->
4063+
Client.Host.set_ssh_enabled_timeout ~rpc ~session_id ~self ~value
4064+
)
4065+
~error:Api_errors.set_ssh_timeout_partially_failed
4066+
4067+
let set_console_timeout ~__context ~self:_ ~value =
4068+
operate ~__context
4069+
~action:(fun ~rpc ~session_id ~self ->
4070+
Client.Host.set_console_idle_timeout ~rpc ~session_id ~self ~value
4071+
)
4072+
~error:Api_errors.set_console_timeout_partially_failed
40594073
end
40604074

40614075
let enable_ssh = Ssh.enable
40624076

40634077
let disable_ssh = Ssh.disable
40644078

4065-
let set_ssh_enabled_timeout ~__context ~self:_ ~value:_ = ()
4079+
let set_ssh_enabled_timeout = Ssh.set_enabled_timeout
40664080

4067-
let set_console_idle_timeout ~__context ~self:_ ~value:_ = ()
4081+
let set_console_idle_timeout = Ssh.set_console_timeout

0 commit comments

Comments
 (0)