%%%------------------------------------------------------------------- %% @doc Read-only administration facade. %%%------------------------------------------------------------------- -module(vpn_admin). -export([dashboard/0, summary/0, summary_view/0, summary_json/0, summary_json_pretty/0, certificate_view/1, extract_cn/1, overview/0, peer_counts/0]). dashboard() -> #{status => vpn_manager:status(), certificates => vpn_manager:certificates()}. summary() -> Status = vpn_manager:status(), Certificates = vpn_manager:certificates(), Counts = peer_counts(), #{counts => Counts#{certificates => length(Certificates)}, peers => [summary_peer(PeerId, PeerStatus, Certificates) || {PeerId, PeerStatus} <- maps:to_list(maps:get(peers, Status, #{}))]}. summary_view() -> Summary = summary(), #{counts => maps:get(counts, Summary, #{}), peers => [peer_view(Peer) || Peer <- maps:get(peers, Summary, [])]}. summary_json() -> try jiffy:encode(summary_view()) catch _:Reason -> jiffy:encode(#{error => <<"summary_generation_failed">>, reason => iolist_to_binary(io_lib:format("~p", [Reason]))}) end. summary_json_pretty() -> summary_json(). overview() -> Counts = peer_counts(), #{configured_peers => maps:get(configured, Counts), running_peers => maps:get(running, Counts), stopped_peers => maps:get(stopped, Counts), certificates => length(vpn_manager:certificates())}. peer_counts() -> Configured = length(vpn_manager:list_peers()), Running = length(vpn_manager:running_peers()), #{configured => Configured, running => Running, stopped => Configured - Running}. summary_peer(PeerId, PeerStatus, Certificates) -> Config = maps:get(config, PeerStatus, #{}), Stats = maps:get(stats, PeerStatus, #{}), LinkStats = maps:get(link, Stats, #{}), Certificate = certificate_for_peer(PeerId, Certificates), #{id => PeerId, running => maps:get(running, PeerStatus, false), mode => maps:get(mode, Config, undefined), ip => maps:get(ip, Config, undefined), remote_peer_id => maps:get(remote_peer_id, Config, undefined), crypto_failures => maps:get(crypto_failures, LinkStats, 0), frames_rejected => maps:get(frames_rejected, LinkStats, 0), certificate => compact_certificate(Certificate)}. certificate_for_peer(PeerId, Certificates) -> case [Certificate || #{peer_id := Id} = Certificate <- Certificates, Id =:= PeerId] of [Certificate | _] -> Certificate; [] -> #{} end. compact_certificate(Certificate) -> maps:with( [subject, issuer, trusted, key_match, not_before, not_after], Certificate). peer_view(Peer) -> #{id => json_value(maps:get(id, Peer, undefined)), running => maps:get(running, Peer, false), mode => json_value(maps:get(mode, Peer, undefined)), ip => json_value(maps:get(ip, Peer, undefined)), remote_peer_id => json_value(maps:get(remote_peer_id, Peer, undefined)), crypto_failures => maps:get(crypto_failures, Peer, 0), frames_rejected => maps:get(frames_rejected, Peer, 0), certificate => certificate_view(maps:get(certificate, Peer, #{}))}. certificate_view(Certificate) -> #{subject_cn => extract_cn(maps:get(subject, Certificate, undefined)), issuer_cn => extract_cn(maps:get(issuer, Certificate, undefined)), trusted => maps:get(trusted, Certificate, false), key_match => maps:get(key_match, Certificate, false), not_before => time_value(maps:get(not_before, Certificate, undefined)), not_after => time_value(maps:get(not_after, Certificate, undefined))}. extract_cn({rdnSequence, RDNs}) -> extract_cn_from_rdns(RDNs); extract_cn({subject, PeerId}) -> json_value(PeerId); extract_cn({issuer, PeerId}) -> json_value(PeerId); extract_cn(Value) -> json_value(Value). extract_cn_from_rdns([[{'AttributeTypeAndValue', {2,5,4,3}, Value} | _] | _]) -> directory_string_value(Value); extract_cn_from_rdns([_ | Rest]) -> extract_cn_from_rdns(Rest); extract_cn_from_rdns([]) -> null. directory_string_value({utf8String, Value}) -> json_value(Value); directory_string_value({printableString, Value}) -> json_value(Value); directory_string_value({teletexString, Value}) -> json_value(Value); directory_string_value({bmpString, Value}) -> json_value(Value); directory_string_value(<<12, Len:8, Value:Len/binary, _/binary>>) -> Value; directory_string_value(Value) -> json_value(Value). time_value({utcTime, Value}) -> json_value(Value); time_value({generalTime, Value}) -> json_value(Value); time_value(Value) -> json_value(Value). json_value(undefined) -> null; json_value(null) -> null; json_value(Value) when is_binary(Value); is_boolean(Value); is_integer(Value); is_float(Value) -> Value; json_value(Value) when is_atom(Value) -> atom_to_binary(Value, utf8); json_value(Value) when is_list(Value) -> unicode:characters_to_binary(Value); json_value(Value) when is_map(Value) -> maps:map(fun(_Key, MapValue) -> json_value(MapValue) end, Value); json_value(_Value) -> null.