~cypheon/ecertmon

6aeafd8d768914ca805c10b4c2e40408e1a14447 — Johann Rudloff 1 year, 9 months ago 887e68d
Start scanner processes supervised

Start the individual scanner processes as part of the app's supervision
tree, so they are restarted in case of a crash. Also increase the time
before the first scan a bit.
4 files changed, 41 insertions(+), 25 deletions(-)

M src/cert_scanner.erl
M src/certmon_app.erl
M src/certmon_sup.erl
M src/metrics_handler.erl
M src/cert_scanner.erl => src/cert_scanner.erl +15 -11
@@ 32,7 32,7 @@ start_link({Hostname, Port}) ->
init([{Hostname, Port}]) ->
  {ok, ScanInterval} = application:get_env(scan_interval),
  logger:info("Cert Scanner initialized for ~s:~p~n", [Hostname, Port]),
  erlang:send_after(1, self(), scan_trigger),
  erlang:send_after(300, self(), scan_trigger),
  Timer = timer:send_interval(ScanInterval, self(), scan_trigger),
  {ok, #state{
          hostname = Hostname,


@@ 50,6 50,19 @@ handle_cast(_Msg, State) ->
  {noreply, State}.

handle_info(scan_trigger, State) ->
  do_scan(State);

handle_info(_Info, State) ->
  {noreply, State}.

terminate(_Reason, _State) ->
  ok.

code_change(_OldVsn, State, _Extra) ->
  {ok, State}.

%% logic
do_scan(State) ->
  Hostname = State#state.hostname,
  {ok, Socket} = ssl:connect(State#state.hostname, State#state.port, []),
  logger:debug("connection established: ~s:~p~n", [Hostname,


@@ 68,16 81,7 @@ handle_info(scan_trigger, State) ->
  NowSeconds = calendar:datetime_to_gregorian_seconds(calendar:universal_time()),
  RemainingDays = NotAfterSeconds - NowSeconds,
  logger:debug("peer cert ~s valid days remaining: ~p~n", [Hostname, RemainingDays / (24 * 3600)]),
  {noreply, State#state{validity = {valid, NotAfterEpoch}}};

handle_info(_Info, State) ->
  {noreply, State}.

terminate(_Reason, _State) ->
  ok.

code_change(_OldVsn, State, _Extra) ->
  {ok, State}.
  {noreply, State#state{validity = {valid, NotAfterEpoch}}}.

%% utilities


M src/certmon_app.erl => src/certmon_app.erl +2 -5
@@ 7,19 7,16 @@
start(_Type, _Args) ->
  {ok, Targets} = application:get_env(targets),
  {ok, Port} = application:get_env(port),
  Scanners = lists:map(
               fun(Target) -> {ok, Pid} = cert_scanner:start_link(Target), {Target, Pid} end,
              Targets),
  Dispatch = cowboy_router:compile([
                                    {'_', [{"/health", healthcheck_handler, []},
                                           {"/metrics", metrics_handler, [{scanners, Scanners}]}]}
                                           {"/metrics", metrics_handler, [{targets, Targets}]}]}
                                   ]),
  {ok, _} = cowboy:start_clear(my_http_listener,
                               [{port, Port}],
                               #{env => #{dispatch => Dispatch}}
                              ),
  sync:go(),
  certmon_sup:start_link().
  certmon_sup:start_link(Targets).

stop(_State) ->
  ok.

M src/certmon_sup.erl => src/certmon_sup.erl +19 -6
@@ 1,12 1,25 @@
-module(certmon_sup).
-behaviour(supervisor).

-export([start_link/0]).
-export([start_link/1]).
-export([init/1]).

start_link() ->
	supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-export([get_scanner_pid/1]).

init([]) ->
	Procs = [],
	{ok, {{one_for_one, 1, 5}, Procs}}.
start_link(Targets) ->
	supervisor:start_link({local, ?MODULE}, ?MODULE, [Targets]).

get_scanner_pid(Target) ->
  case proplists:lookup(Target, supervisor:which_children(?MODULE)) of
    {Target, ChildPid, _, _} -> {ok, ChildPid};
    none -> {error, notfound}
  end.

init([Targets]) ->
  Procs = lists:map(
            fun(Target) -> #{
                             id => Target,
                             start => {cert_scanner, start_link, [Target]}
                 } end,
            Targets),
	{ok, {{one_for_one, 10, 30}, Procs}}.

M src/metrics_handler.erl => src/metrics_handler.erl +5 -3
@@ 3,7 3,9 @@

-export([init/2]).

format_metric({{Hostname, Port}, ScannerPid}) ->
format_metric({Hostname, Port}) ->
  Target = {Hostname, Port},
  {ok, ScannerPid} = certmon_sup:get_scanner_pid(Target),
  Valid = try gen_server:call(ScannerPid, {get_status}) of
            {valid, Epoch} -> Epoch;
            _ -> 0


@@ 17,9 19,9 @@ format_metric({{Hostname, Port}, ScannerPid}) ->
  ].

init(Req0, State) ->
  {scanners, Scanners} = proplists:lookup(scanners, State),
  {targets, Targets} = proplists:lookup(targets, State),
  Req = cowboy_req:reply(200,
                         #{<<"content-type">> => <<"text/plain; version=0.0.4">>},
                         lists:map(fun format_metric/1, Scanners),
                         lists:map(fun format_metric/1, Targets),
                         Req0),
  {ok, Req, State}.