-
Notifications
You must be signed in to change notification settings - Fork 8
/
admin.ml
171 lines (159 loc) · 7.04 KB
/
admin.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
let ( // ) = Fpath.( / )
let with_file_out ~flags file f =
let flags = Unix.O_WRONLY::Unix.O_CREAT::Unix.O_TRUNC::Unix.O_NONBLOCK::flags in
Lwt_io.with_file ~flags ~mode:Lwt_io.Output file f
let is_username_char = function
| 'a'..'z' -> true
| '-' | '_' -> true
| _ -> false
let get_keyfile workdir username =
(* TODO: Replace this test by Oca_lib.is_valid_filename ? *)
if String.is_empty username || not (String.for_all is_username_char username) then
failwith "Invalid username";
Server_workdirs.keyfile ~username workdir
let create_userkey workdir username =
let keyfile = get_keyfile workdir username in
let key = Mirage_crypto_pk.Rsa.generate ~bits:2048 () in
let key = Mirage_crypto_pk.Rsa.sexp_of_priv key in
let key = Sexplib.Sexp.to_string key in
with_file_out ~flags:[Unix.O_EXCL] (Fpath.to_string keyfile) begin fun chan ->
Lwt_io.write_line chan key
end
let create_admin_key workdir =
let username = Oca_lib.default_admin_name in
let keyfile = get_keyfile workdir username in
match%lwt Lwt_unix.file_exists (Fpath.to_string keyfile) with
| true -> Lwt.return_unit
| false -> create_userkey workdir username
let get_log workdir =
let ilogdir = Server_workdirs.ilogdir workdir in
let%lwt logs = Oca_lib.get_files ilogdir in
let logs = List.sort String.compare logs in
let logfile = Option.get_exn_or "no last log" (List.last_opt logs) in
let logfile = ilogdir // logfile in
let%lwt fd = Lwt_unix.openfile (Fpath.to_string logfile) Unix.[O_RDONLY] 0o644 in
let off = ref 0 in
let rec loop () =
let is_running = Check.is_running () in
let%lwt new_off = Lwt_unix.lseek fd 0 Unix.SEEK_END in
if new_off < 0 then
assert false
else if !off < new_off then begin
let%lwt _ = Lwt_unix.lseek fd !off Unix.SEEK_SET in
let len = new_off - !off in
let buf = Bytes.create len in
let%lwt _ = Lwt_unix.read fd buf 0 len in
off := new_off;
Lwt.return (Some (Bytes.to_string buf))
end else if is_running then begin
off := new_off;
let%lwt () = Lwt_unix.sleep 1. in
loop ()
end else
Lwt.return_none
in
Lwt.return loop
let admin_action ~on_finished ~conf ~run_trigger workdir body =
let%lwt resp =
match String.split_on_char '\n' body with
| ["set-auto-run-interval"; i] ->
let%lwt () = Server_configfile.set_auto_run_interval conf (int_of_string i) in
Lwt.return (fun () -> Lwt.return_none)
| ["set-processes"; i] ->
let i = int_of_string i in
if i < 0 then
Lwt.fail (Failure "Cannot set the number of processes to a negative value.")
else
let%lwt () = Server_configfile.set_processes conf i in
Lwt.return (fun () -> Lwt.return_none)
| ["add-ocaml-switch";name;switch] ->
let switch = Intf.Switch.create ~name ~switch in
let switches = Option.get_or ~default:[] (Server_configfile.ocaml_switches conf) in
if List.mem ~eq:Intf.Switch.equal switch switches then
Lwt.fail (Failure "Cannot have duplicate switches names.")
else
let switches = List.sort Intf.Switch.compare (switch :: switches) in
let%lwt () = Server_configfile.set_ocaml_switches conf switches in
Lwt.return (fun () -> Lwt.return_none)
| ["set-ocaml-switch";name;switch] ->
let switch = Intf.Switch.create ~name ~switch in
let switches = Option.get_or ~default:[] (Server_configfile.ocaml_switches conf) in
let idx, _ = Option.get_exn_or "can't find switch name" (List.find_idx (Intf.Switch.equal switch) switches) in
let switches = List.set_at_idx idx switch switches in
let%lwt () = Server_configfile.set_ocaml_switches conf switches in
Lwt.return (fun () -> Lwt.return_none)
| ["rm-ocaml-switch";name] ->
let switch = Intf.Switch.create ~name ~switch:"(* TODO: remove this shit *)" in
let switches = Option.get_or ~default:[] (Server_configfile.ocaml_switches conf) in
let switches = List.remove ~eq:Intf.Switch.equal ~key:switch switches in
let%lwt () = Server_configfile.set_ocaml_switches conf switches in
Lwt.return (fun () -> Lwt.return_none)
| "set-slack-webhooks"::webhooks ->
let webhooks = List.map Uri.of_string webhooks in
let%lwt () = Server_configfile.set_slack_webhooks conf webhooks in
Lwt.return (fun () -> Lwt.return_none)
| ["set-list-command";cmd] ->
let%lwt () = Server_configfile.set_list_command conf cmd in
Lwt.return (fun () -> Lwt.return_none)
| ["run"] ->
let%lwt () = Lwt_mvar.put run_trigger () in
Lwt.return (fun () -> Lwt.return_none)
| ["add-user";username] ->
let%lwt () = create_userkey workdir username in
Lwt.return (fun () -> Lwt.return_none)
| ["clear-cache"] ->
let%lwt () = on_finished workdir in
Lwt.return (fun () -> Lwt.return_none)
| ["log"] ->
get_log workdir
| _ ->
Lwt.fail (Failure "Action unrecognized.")
in
let stream = Lwt_stream.from resp in
Cohttp_lwt_unix.Server.respond ~status:`OK ~body:(`Stream stream) ()
let is_bzero = function
| '\000' -> true
| _ -> false
let get_user_key workdir user =
let keyfile = get_keyfile workdir user in
let%lwt key = Lwt_io.with_file ~mode:Lwt_io.Input (Fpath.to_string keyfile) (Lwt_io.read ?count:None) in
Lwt.return (Mirage_crypto_pk.Rsa.priv_of_sexp (Sexplib.Sexp.of_string key))
let partial_decrypt key msg =
Cstruct.to_string (Mirage_crypto_pk.Rsa.decrypt ~key (Cstruct.of_string msg))
let rec decrypt key msg =
let key_size = Mirage_crypto_pk.Rsa.priv_bits key / 8 in
if String.length msg <= key_size then
String.drop_while is_bzero (partial_decrypt key msg)
else
let msg, next = String.take_drop key_size msg in
partial_decrypt key msg ^ decrypt key next
let callback ~on_finished ~conf ~run_trigger workdir _conn _req body =
let%lwt body = Cohttp_lwt.Body.to_string body in
match String.Split.left ~by:"\n" body with
| Some (pversion, body) when String.equal Oca_lib.protocol_version pversion ->
begin match String.Split.left ~by:"\n" body with
| Some (_, "") ->
Lwt.fail (Failure "Empty message")
| Some (user, body) ->
let%lwt key = get_user_key workdir user in
let body = decrypt key body in
begin match String.Split.left ~by:"\n" body with
| Some (user', body) when String.equal user user' ->
admin_action ~on_finished ~conf ~run_trigger workdir body
| Some _ ->
Lwt.fail (Failure "Identity couldn't be ensured")
| None ->
Lwt.fail (Failure "Identity check required")
end
| None ->
Lwt.fail (Failure "Cannot find username")
end
| Some (pversion, _) ->
Cohttp_lwt_unix.Server.respond_string
~status:`Upgrade_required
~body:("This server requires opam-health-check protocol version \
'"^Oca_lib.protocol_version^"' but got '"^pversion^"'. \
Please upgrade your client.")
()
| None ->
Lwt.fail (Failure "Cannot parse request")