Skip to content

Commit 2b4cf41

Browse files
committed
CP-53478: Implement SSH-related APIs for Dom0 SSH control
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. Updated `records.ml` to support `host-param-set/get/list` and `pool-param-set/get/list` for SSH-related fields. Signed-off-by: Lunfan Zhang <[email protected]>
1 parent 2f00a21 commit 2b4cf41

File tree

5 files changed

+267
-9
lines changed

5 files changed

+267
-9
lines changed

ocaml/idl/datamodel_errors.ml

+6
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 of hosts failed to set SSH timeout." () ;
2045+
2046+
error Api_errors.set_console_timeout_partially_failed ["hosts"]
2047+
~doc:"Some of 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

+77
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,23 @@ let get_pbds_host rpc session_id pbds =
204204
let get_sr_host rpc session_id record =
205205
get_pbds_host rpc session_id record.API.sR_PBDs
206206

207+
(** Get a field from the first host, or return a default value if the field
208+
is not the same on all hosts. *)
209+
let get_consistent_field_or_default ~rpc ~session_id ~getter ~transform ~default
210+
=
211+
match Client.Host.get_all ~rpc ~session_id with
212+
| [] ->
213+
default
214+
| h :: hs ->
215+
let first_value = getter ~rpc ~session_id ~self:h |> transform in
216+
let all_values =
217+
List.map (fun h -> getter ~rpc ~session_id ~self:h |> transform) hs
218+
in
219+
if List.for_all (( = ) first_value) all_values then
220+
first_value
221+
else
222+
default
223+
207224
let bond_record rpc session_id bond =
208225
let _ref = ref bond in
209226
let empty_record =
@@ -1506,6 +1523,44 @@ let pool_record rpc session_id pool =
15061523
)
15071524
~get_map:(fun () -> (x ()).API.pool_license_server)
15081525
()
1526+
; make_field ~name:"ssh-enabled"
1527+
~get:(fun () ->
1528+
get_consistent_field_or_default ~rpc ~session_id
1529+
~getter:Client.Host.get_ssh_enabled ~transform:string_of_bool
1530+
~default:""
1531+
)
1532+
()
1533+
; make_field ~name:"ssh-enabled-timeout"
1534+
~get:(fun () ->
1535+
get_consistent_field_or_default ~rpc ~session_id
1536+
~getter:Client.Host.get_ssh_enabled_timeout
1537+
~transform:Int64.to_string ~default:""
1538+
)
1539+
~set:(fun value ->
1540+
let minutes = safe_i64_of_string "ssh-enabled-timeout" value in
1541+
let seconds = Int64.mul minutes 60L in
1542+
Client.Pool.set_ssh_enabled_timeout ~rpc ~session_id ~self:pool
1543+
~value:seconds
1544+
)
1545+
()
1546+
; make_field ~name:"ssh-expiry"
1547+
~get:(fun () ->
1548+
get_consistent_field_or_default ~rpc ~session_id
1549+
~getter:Client.Host.get_ssh_expiry ~transform:Date.to_rfc3339
1550+
~default:""
1551+
)
1552+
()
1553+
; make_field ~name:"console-idle-timeout"
1554+
~get:(fun () ->
1555+
get_consistent_field_or_default ~rpc ~session_id
1556+
~getter:Client.Host.get_console_idle_timeout
1557+
~transform:Int64.to_string ~default:""
1558+
)
1559+
~set:(fun value ->
1560+
Client.Pool.set_console_idle_timeout ~rpc ~session_id ~self:pool
1561+
~value:(Int64.of_string value)
1562+
)
1563+
()
15091564
]
15101565
}
15111566

@@ -3265,6 +3320,28 @@ let host_record rpc session_id host =
32653320
; make_field ~name:"last-update-hash"
32663321
~get:(fun () -> (x ()).API.host_last_update_hash)
32673322
()
3323+
; make_field ~name:"ssh-enabled"
3324+
~get:(fun () -> string_of_bool (x ()).API.host_ssh_enabled)
3325+
()
3326+
; make_field ~name:"ssh-enabled-timeout"
3327+
~get:(fun () -> Int64.to_string (x ()).API.host_ssh_enabled_timeout)
3328+
~set:(fun value ->
3329+
let minutes = safe_i64_of_string "ssh-enabled-timeout" value in
3330+
let seconds = Int64.mul minutes 60L in
3331+
Client.Host.set_ssh_enabled_timeout ~rpc ~session_id ~self:host
3332+
~value:seconds
3333+
)
3334+
()
3335+
; make_field ~name:"ssh-expiry"
3336+
~get:(fun () -> Date.to_rfc3339 (x ()).API.host_ssh_expiry)
3337+
()
3338+
; make_field ~name:"console-idle-timeout"
3339+
~get:(fun () -> Int64.to_string (x ()).API.host_console_idle_timeout)
3340+
~set:(fun value ->
3341+
Client.Host.set_console_idle_timeout ~rpc ~session_id ~self:host
3342+
~value:(safe_i64_of_string "console-idle-timeout" value)
3343+
)
3344+
()
32683345
]
32693346
}
32703347

ocaml/xapi-consts/api_errors.ml

+6
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_host.ml

+162-7
Original file line numberDiff line numberDiff line change
@@ -3114,26 +3114,181 @@ let emergency_clear_mandatory_guidance ~__context =
31143114
) ;
31153115
Db.Host.set_pending_guidances ~__context ~self ~value:[]
31163116

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

31273188
let disable_ssh ~__context ~self =
31283189
try
3190+
debug "Disabling SSH for host %s" (Db.Host.get_uuid ~__context ~self) ;
3191+
3192+
remove_disable_ssh_job ~__context ~self ;
3193+
31293194
Xapi_systemctl.disable ~wait_until_success:false "sshd" ;
3130-
Xapi_systemctl.stop ~wait_until_success:false "sshd"
3131-
with _ ->
3195+
Xapi_systemctl.stop ~wait_until_success:false "sshd" ;
3196+
Db.Host.set_ssh_enabled ~__context ~self ~value:false ;
3197+
3198+
let expiry_time =
3199+
Ptime_clock.now () |> Ptime.to_float_s |> Date.of_unix_time
3200+
in
3201+
Db.Host.set_ssh_expiry ~__context ~self ~value:expiry_time
3202+
with e ->
3203+
error "Failed to disable SSH on host %s: %s" (Ref.string_of self)
3204+
(Printexc.to_string e) ;
31323205
raise
31333206
(Api_errors.Server_error
31343207
(Api_errors.disable_ssh_failed, [Ref.string_of self])
31353208
)
31363209

3137-
let set_ssh_enabled_timeout ~__context ~self:_ ~value:_ = ()
3210+
let set_ssh_enabled_timeout ~__context ~self ~value =
3211+
let validate_timeout value =
3212+
if value < 0L || value > 172800L then
3213+
raise
3214+
(Api_errors.Server_error
3215+
( Api_errors.invalid_value
3216+
, [
3217+
"timeout"
3218+
; Int64.to_string value
3219+
; "must be between 0 and 2*24*60*60 seconds"
3220+
]
3221+
)
3222+
)
3223+
else
3224+
value
3225+
in
3226+
let timeout = validate_timeout value in
3227+
debug "Setting SSH timeout for host %s to %Ld seconds"
3228+
(Db.Host.get_uuid ~__context ~self)
3229+
timeout ;
3230+
Db.Host.set_ssh_enabled_timeout ~__context ~self ~value:timeout ;
3231+
match Db.Host.get_ssh_enabled ~__context ~self with
3232+
| false ->
3233+
()
3234+
| true -> (
3235+
match timeout with
3236+
| 0L ->
3237+
remove_disable_ssh_job ~__context ~self ;
3238+
Db.Host.set_ssh_expiry ~__context ~self ~value:Date.epoch
3239+
| t ->
3240+
schedule_disable_ssh_job ~__context ~self ~timeout:t ;
3241+
Db.Host.set_ssh_enabled_timeout ~__context ~self ~value:timeout
3242+
)
3243+
3244+
let set_console_idle_timeout ~__context ~self ~value =
3245+
let validate_timeout = function
3246+
| timeout when timeout >= 0L ->
3247+
timeout
3248+
| timeout ->
3249+
raise
3250+
(Api_errors.Server_error
3251+
( Api_errors.invalid_value
3252+
, [
3253+
"console_timeout"
3254+
; Int64.to_string timeout
3255+
; "must be a positive integer"
3256+
]
3257+
)
3258+
)
3259+
in
3260+
3261+
let console_timeout = validate_timeout value in
3262+
let bashrc = "/root/.bashrc" in
31383263

3139-
let set_console_idle_timeout ~__context ~self:_ ~value:_ = ()
3264+
try
3265+
let lines = Unixext.read_lines ~path:bashrc in
3266+
let filtered =
3267+
List.filter
3268+
(fun line -> not (String.starts_with ~prefix:"export TMOUT=" line))
3269+
lines
3270+
in
3271+
3272+
let new_lines =
3273+
match console_timeout with
3274+
| 0L ->
3275+
filtered
3276+
| timeout ->
3277+
filtered @ [Printf.sprintf "export TMOUT=%Ld" timeout]
3278+
in
3279+
let content = String.concat "\n" new_lines ^ "\n" in
3280+
3281+
Unixext.atomic_write_to_file bashrc 0o0644 (fun fd ->
3282+
Unix.write fd (Bytes.of_string content) 0 (String.length content)
3283+
|> ignore
3284+
) ;
3285+
3286+
Db.Host.set_console_idle_timeout ~__context ~self ~value:console_timeout
3287+
with e ->
3288+
error "Failed to configure console timeout: %s" (Printexc.to_string e) ;
3289+
raise
3290+
(Api_errors.Server_error
3291+
( Api_errors.set_console_idle_timeout_failed
3292+
, ["Failed to configure console timeout"; Printexc.to_string e]
3293+
)
3294+
)

ocaml/xapi/xapi_pool.ml

+16-2
Original file line numberDiff line numberDiff line change
@@ -4003,12 +4003,26 @@ module Ssh = struct
40034003
let disable ~__context ~self:_ =
40044004
operate ~__context ~action:Client.Host.disable_ssh
40054005
~error:Api_errors.disable_ssh_partially_failed
4006+
4007+
let set_enabled_timeout ~__context ~self:_ ~value =
4008+
operate ~__context
4009+
~action:(fun ~rpc ~session_id ~self ->
4010+
Client.Host.set_ssh_enabled_timeout ~rpc ~session_id ~self ~value
4011+
)
4012+
~error:Api_errors.set_ssh_timeout_partially_failed
4013+
4014+
let set_console_timeout ~__context ~self:_ ~value =
4015+
operate ~__context
4016+
~action:(fun ~rpc ~session_id ~self ->
4017+
Client.Host.set_console_idle_timeout ~rpc ~session_id ~self ~value
4018+
)
4019+
~error:Api_errors.set_console_timeout_partially_failed
40064020
end
40074021

40084022
let enable_ssh = Ssh.enable
40094023

40104024
let disable_ssh = Ssh.disable
40114025

4012-
let set_ssh_enabled_timeout ~__context ~self:_ ~value:_ = ()
4026+
let set_ssh_enabled_timeout = Ssh.set_enabled_timeout
40134027

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

0 commit comments

Comments
 (0)