%%%------------------------------------------------------------------- %% @doc Read-only VPN management API. %%%------------------------------------------------------------------- -module(vpn_manager). -export([list_peers/0, running_peers/0, status/0, peer_status/1, certificates/0, certificate_info/1, certificate_status/1, peer_info/1, peer_stats/1, start_peer/1, stop_peer/1, reload_config/0, peer_running/1, find_peer/1]). list_peers() -> configured_peer_ids(). running_peers() -> running_peer_ids(). status() -> Configured = list_peers(), Running = running_peers(), #{configured => Configured, running => Running, peers => maps:from_list([{PeerId, peer_status(PeerId)} || PeerId <- Configured])}. certificates() -> [certificate_status(PeerId) || PeerId <- list_peers()]. certificate_info(PeerId) -> case lists:member(PeerId, list_peers()) of true -> certificate_status(PeerId); false -> {error, not_found} end. certificate_status(PeerId) -> case lists:member(PeerId, running_peers()) of true -> running_certificate_status(PeerId); false -> stopped_certificate_status(PeerId) end. peer_status(PeerId) -> case lists:member(PeerId, running_peers()) of true -> running_peer_status(PeerId); false -> #{running => false} end. peer_info(PeerId) -> case find_peer(PeerId) of {ok, Pid} -> #{id => PeerId, identity => vpn_peer:identity_info(Pid), config => vpn_peer:config(Pid)}; {error, not_found} -> {error, not_found} end. peer_stats(PeerId) -> case find_peer(PeerId) of {ok, Pid} -> vpn_peer:stats(Pid); {error, not_found} -> {error, not_found} end. running_peer_status(PeerId) -> case peer_info(PeerId) of #{identity := Identity, config := Config} -> case peer_stats(PeerId) of #{id := _PeerId} = Stats -> #{running => true, identity => Identity, config => Config, stats => Stats, certificate => certificate_summary(Identity)}; {error, Reason} -> #{running => false, error => Reason} end; {error, Reason} -> #{running => false, error => Reason} end. running_certificate_status(PeerId) -> case peer_info(PeerId) of #{identity := Identity} -> certificate_entry(PeerId, true, Identity); {error, Reason} -> #{peer_id => PeerId, running => false, error => Reason} end. stopped_certificate_status(PeerId) -> case find_peer_config(PeerId) of {ok, PeerConfig} -> case vpn_identity:load(PeerConfig) of {ok, Identity} -> certificate_entry(PeerId, false, vpn_identity:safe_info(Identity)); {error, Reason} -> #{peer_id => PeerId, running => false, error => Reason} end; {error, not_found} -> #{peer_id => PeerId, running => false, error => not_found} end. certificate_entry(PeerId, Running, Identity) -> Certificate = maps:get(certificate, Identity, #{}), #{peer_id => PeerId, running => Running, trusted => maps:get(trusted, Identity, false), key_match => maps:get(key_match, Identity, false), subject => maps:get(subject, Certificate, undefined), issuer => maps:get(issuer, Certificate, undefined), serial_number => maps:get(serial_number, Certificate, undefined), not_before => maps:get(not_before, Certificate, undefined), not_after => maps:get(not_after, Certificate, undefined), certificate_path => maps:get(certificate_path, Identity, undefined)}. certificate_summary(Identity) -> Certificate = maps:get(certificate, Identity, #{}), #{trusted => maps:get(trusted, Identity, false), key_match => maps:get(key_match, Identity, false), subject => maps:get(subject, Certificate, undefined), issuer => maps:get(issuer, Certificate, undefined), serial_number => maps:get(serial_number, Certificate, undefined), not_before => maps:get(not_before, Certificate, undefined), not_after => maps:get(not_after, Certificate, undefined), certificate_path => maps:get(certificate_path, Identity, undefined)}. reload_config() -> ConfiguredIds = configured_peer_ids(), RunningIds = running_peer_ids(), ToStop = RunningIds -- ConfiguredIds, ToStart = ConfiguredIds -- RunningIds, Unchanged = RunningIds -- ToStop, StopResult = collect_stop_results(ToStop, #{started => [], stopped => [], failed => []}), StartResult = collect_start_results(ToStart, StopResult), StartResult#{unchanged => Unchanged}. start_peer(PeerId) -> case peer_running(PeerId) of true -> {error, already_started}; false -> start_configured_peer(PeerId) end. stop_peer(PeerId) -> case find_peer(PeerId) of {ok, _Pid} -> vpn_peer_sup:stop_peer(PeerId); {error, not_found} -> {error, not_found} end. peer_running(PeerId) -> case find_peer(PeerId) of {ok, _Pid} -> true; {error, not_found} -> false end. find_peer(PeerId) -> case [Pid || {{vpn_peer, Id}, Pid, worker, _Modules} <- peer_children(), Id =:= PeerId, is_pid(Pid)] of [Pid | _] -> {ok, Pid}; [] -> {error, not_found} end. start_configured_peer(PeerId) -> case find_peer_config(PeerId) of {ok, PeerConfig} -> case vpn_peer_sup:start_peer(PeerConfig) of {ok, Pid} -> {ok, Pid}; {ok, Pid, _Info} -> {ok, Pid}; {error, {already_started, _Pid}} -> {error, already_started}; {error, {Reason, {child, _Pid, _Id, _Start, _Restart, _Significant, _Shutdown, _Type, _Modules}}} -> {error, Reason}; {error, Reason} -> {error, Reason} end; {error, not_found} -> {error, not_found} end. find_peer_config(PeerId) -> case [PeerConfig || PeerConfig <- configured_peers(), maps:get(id, PeerConfig) =:= PeerId] of [PeerConfig | _] -> {ok, PeerConfig}; [] -> {error, not_found} end. collect_stop_results([], Acc) -> Acc; collect_stop_results([PeerId | Rest], Acc) -> case stop_peer(PeerId) of ok -> collect_stop_results(Rest, append_result(stopped, PeerId, Acc)); {error, Reason} -> collect_stop_results(Rest, append_result(failed, {PeerId, Reason}, Acc)) end. collect_start_results([], Acc) -> Acc; collect_start_results([PeerId | Rest], Acc) -> case start_peer(PeerId) of {ok, _Pid} -> collect_start_results(Rest, append_result(started, PeerId, Acc)); {error, Reason} -> collect_start_results(Rest, append_result(failed, {PeerId, Reason}, Acc)) end. append_result(Key, Value, Acc) -> maps:update_with(Key, fun(Values) -> Values ++ [Value] end, [Value], Acc). configured_peer_ids() -> lists:sort([maps:get(id, PeerConfig) || PeerConfig <- configured_peers()]). running_peer_ids() -> lists:sort([PeerId || {{vpn_peer, PeerId}, Pid, worker, _Modules} <- peer_children(), is_pid(Pid)]). configured_peers() -> application:get_env(vpn, peers, []). peer_children() -> try supervisor:which_children(vpn_peer_sup) of Children -> Children catch exit:{noproc, _} -> [] end.