parent
4f5706c174
commit
fe2bd8d95e
|
@ -14,7 +14,7 @@
|
|||
## Copyright (c) 2007-2019 Pivotal Software, Inc. All rights reserved.
|
||||
|
||||
defmodule RabbitMQ.CLI.Core.Listeners do
|
||||
import Record, only: [defrecord: 2, extract: 2]
|
||||
import Record, only: [defrecord: 3, extract: 2]
|
||||
import RabbitCommon.Records
|
||||
import Rabbitmq.Atom.Coerce
|
||||
|
||||
|
@ -22,7 +22,9 @@ defmodule RabbitMQ.CLI.Core.Listeners do
|
|||
# API
|
||||
#
|
||||
|
||||
defrecord :hostent, extract(:hostent, from_lib: "kernel/include/inet.hrl")
|
||||
defrecord :certificate, :Certificate, extract(:Certificate, from_lib: "public_key/include/public_key.hrl")
|
||||
defrecord :tbscertificate, :TBSCertificate, extract(:TBSCertificate, from_lib: "public_key/include/public_key.hrl")
|
||||
defrecord :validity, :Validity, extract(:Validity, from_lib: "public_key/include/public_key.hrl")
|
||||
|
||||
def listeners_on(listeners, target_node) do
|
||||
Enum.filter(listeners, fn listener(node: node) ->
|
||||
|
@ -106,6 +108,100 @@ defmodule RabbitMQ.CLI.Core.Listeners do
|
|||
end
|
||||
end
|
||||
|
||||
def listener_expiring_within(listener, seconds) do
|
||||
listener(node: node, protocol: protocol, ip_address: interface, port: port, opts: opts) = listener
|
||||
certfile = Keyword.get(opts, :certfile)
|
||||
cacertfile = Keyword.get(opts, :cacertfile)
|
||||
now = :calendar.datetime_to_gregorian_seconds(:calendar.universal_time())
|
||||
expiry_date = now + seconds
|
||||
certfile_expires_on = expired(cert_validity(read_cert(certfile)), expiry_date)
|
||||
cacertfile_expires_on = expired(cert_validity(read_cert(cacertfile)), expiry_date)
|
||||
case {certfile_expires_on, cacertfile_expires_on} do
|
||||
{[], []} ->
|
||||
false
|
||||
_ ->
|
||||
%{
|
||||
node: node,
|
||||
protocol: protocol,
|
||||
interface: interface,
|
||||
port: port,
|
||||
certfile: certfile,
|
||||
cacertfile: cacertfile,
|
||||
certfile_expires_on: certfile_expires_on,
|
||||
cacertfile_expires_on: cacertfile_expires_on
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def expired_listener_map(%{node: node, protocol: protocol, interface: interface, port: port, certfile_expires_on: certfile_expires_on, cacertfile_expires_on: cacertfile_expires_on, certfile: certfile, cacertfile: cacertfile}) do
|
||||
%{
|
||||
node: node,
|
||||
protocol: protocol,
|
||||
interface: :inet.ntoa(interface) |> to_string |> maybe_enquote_interface,
|
||||
port: port,
|
||||
purpose: protocol_label(to_atom(protocol)),
|
||||
certfile: certfile |> to_string,
|
||||
cacertfile: cacertfile |> to_string,
|
||||
certfile_expires_on: expires_on_list(certfile_expires_on),
|
||||
cacertfile_expires_on: expires_on_list(cacertfile_expires_on)
|
||||
}
|
||||
end
|
||||
|
||||
def expires_on_list({:error, _} = error) do
|
||||
[error]
|
||||
end
|
||||
def expires_on_list(expires) do
|
||||
Enum.map(expires, &expires_on/1)
|
||||
end
|
||||
|
||||
def expires_on({:error, _} = error) do
|
||||
error
|
||||
end
|
||||
def expires_on(seconds) do
|
||||
{:ok, naive} = NaiveDateTime.from_erl(:calendar.gregorian_seconds_to_datetime(seconds))
|
||||
NaiveDateTime.to_string(naive)
|
||||
end
|
||||
|
||||
def expired(nil, _) do
|
||||
[]
|
||||
end
|
||||
def expired({:error, _} = error, _) do
|
||||
error
|
||||
end
|
||||
def expired(expires, expiry_date) do
|
||||
Enum.filter(expires, fn ({:error, _} = e) -> true
|
||||
(seconds) -> seconds < expiry_date end)
|
||||
end
|
||||
|
||||
def cert_validity(nil) do
|
||||
nil
|
||||
end
|
||||
def cert_validity(cert) do
|
||||
dsa_entries = :public_key.pem_decode(cert)
|
||||
case dsa_entries do
|
||||
[] ->
|
||||
{:error, "The certificate file provided does not contain any PEM entry."}
|
||||
_ ->
|
||||
now = :calendar.datetime_to_gregorian_seconds(:calendar.universal_time())
|
||||
Enum.map(dsa_entries, fn ({:Certificate, _, _} = dsa_entry) ->
|
||||
certificate(tbsCertificate: tbs_certificate) = :public_key.pem_entry_decode(dsa_entry)
|
||||
tbscertificate(validity: validity) = tbs_certificate
|
||||
validity(notAfter: not_after, notBefore: not_before) = validity
|
||||
start = :pubkey_cert.time_str_2_gregorian_sec(not_before)
|
||||
case start > now do
|
||||
true ->
|
||||
{:ok, naive} = NaiveDateTime.from_erl(:calendar.gregorian_seconds_to_datetime(start))
|
||||
startdate = NaiveDateTime.to_string(naive)
|
||||
{:error, "Certificate is not yet valid. It starts on #{startdate}"}
|
||||
false ->
|
||||
:pubkey_cert.time_str_2_gregorian_sec(not_after)
|
||||
end
|
||||
({type, _, _}) ->
|
||||
{:error, "The certificate file provided contains a #{type} entry."}
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def listener_rows(listeners) do
|
||||
for listener(node: node, protocol: protocol, ip_address: interface, port: port) <- listeners do
|
||||
# Listener options are left out intentionally, see above
|
||||
|
|
95
deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_certificate_expiration.ex
vendored
Normal file
95
deps/rabbitmq_cli/lib/rabbitmq/cli/diagnostics/commands/check_certificate_expiration.ex
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
## The contents of this file are subject to the Mozilla Public License
|
||||
## Version 1.1 (the "License"); you may not use this file except in
|
||||
## compliance with the License. You may obtain a copy of the License
|
||||
## at https://www.mozilla.org/MPL/
|
||||
##
|
||||
## Software distributed under the License is distributed on an "AS IS"
|
||||
## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
## the License for the specific language governing rights and
|
||||
## limitations under the License.
|
||||
##
|
||||
## The Original Code is RabbitMQ.
|
||||
##
|
||||
## The Initial Developer of the Original Code is GoPivotal, Inc.
|
||||
## Copyright (c) 2007-2019 Pivotal Software, Inc. All rights reserved.
|
||||
|
||||
defmodule RabbitMQ.CLI.Diagnostics.Commands.CheckCertificateExpirationCommand do
|
||||
alias RabbitMQ.CLI.Core.DocGuide
|
||||
alias RabbitMQ.CLI.TimeUnit, as: TU
|
||||
@behaviour RabbitMQ.CLI.CommandBehaviour
|
||||
|
||||
import RabbitMQ.CLI.Core.Listeners
|
||||
|
||||
def switches(), do: [unit: :string, within: :integer]
|
||||
|
||||
def merge_defaults(args, opts) do
|
||||
{args, Map.merge(%{unit: "days", within: 1}, opts)}
|
||||
end
|
||||
|
||||
def validate(args, _) when length(args) > 0 do
|
||||
{:validation_failure, :too_many_args}
|
||||
end
|
||||
def validate(_, %{unit: unit}) do
|
||||
case TU.known_unit?(unit) do
|
||||
true ->
|
||||
:ok
|
||||
|
||||
false ->
|
||||
{:validation_failure, "unit '#{unit}' is not supported. Please use one of: days, weeks, months, years"}
|
||||
end
|
||||
end
|
||||
def validate(_, _), do: :ok
|
||||
|
||||
def run([], %{node: node_name, unit: unit, within: within, timeout: timeout}) do
|
||||
case :rabbit_misc.rpc_call(node_name, :rabbit_networking, :active_listeners, [], timeout) do
|
||||
{:error, _} = err ->
|
||||
err
|
||||
|
||||
{:error, _, _} = err ->
|
||||
err
|
||||
|
||||
{:badrpc, _} = err ->
|
||||
err
|
||||
|
||||
xs when is_list(xs) ->
|
||||
listeners = listeners_on(xs, node_name)
|
||||
seconds = TU.convert(within, unit)
|
||||
Enum.reduce(listeners, [], fn (listener, acc) -> case listener_expiring_within(listener, seconds) do
|
||||
false -> acc
|
||||
expiring -> [expiring | acc]
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def output([], %{formatter: "json"}) do
|
||||
{:ok, %{"result" => "ok"}}
|
||||
end
|
||||
|
||||
def output([], %{unit: unit, within: within}) do
|
||||
{:ok, "No certificates are expiring within #{within} #{unit}."}
|
||||
end
|
||||
|
||||
def output(listeners, %{formatter: "json"}) do
|
||||
{:error, :check_failed, %{"result" => "error", "expired" => Enum.map(listeners, &expired_listener_map/1)}}
|
||||
end
|
||||
|
||||
def output(listeners, %{}) do
|
||||
{:error, :check_failed, Enum.map(listeners, &expired_listener_map/1)}
|
||||
end
|
||||
|
||||
def usage, do: "check_certificate_expiration"
|
||||
|
||||
def usage_doc_guides() do
|
||||
[
|
||||
DocGuide.configuration(),
|
||||
DocGuide.tls()
|
||||
]
|
||||
end
|
||||
|
||||
def help_section(), do: :observability_and_health_checks
|
||||
|
||||
def description(), do: "Checks the expiration date on the certificates for every listener configured to use TLS"
|
||||
|
||||
def banner(_, %{node: node_name}), do: "Expired certificates of node #{node_name} ..."
|
||||
end
|
|
@ -0,0 +1,57 @@
|
|||
## The contents of this file are subject to the Mozilla Public License
|
||||
## Version 1.1 (the "License"); you may not use this file except in
|
||||
## compliance with the License. You may obtain a copy of the License
|
||||
## at https://www.mozilla.org/MPL/
|
||||
##
|
||||
## Software distributed under the License is distributed on an "AS IS"
|
||||
## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
## the License for the specific language governing rights and
|
||||
## limitations under the License.
|
||||
##
|
||||
## The Original Code is RabbitMQ.
|
||||
##
|
||||
## The Initial Developer of the Original Code is GoPivotal, Inc.
|
||||
## Copyright (c) 2007-2019 Pivotal Software, Inc. All rights reserved.
|
||||
|
||||
defmodule RabbitMQ.CLI.TimeUnit do
|
||||
require MapSet
|
||||
|
||||
@days_seconds 86400
|
||||
@weeks_seconds @days_seconds * 7
|
||||
@months_seconds @days_seconds * (365 / 12)
|
||||
@years_seconds @days_seconds * 365
|
||||
|
||||
def known_units() do
|
||||
MapSet.new([
|
||||
"days",
|
||||
"weeks",
|
||||
"months",
|
||||
"years"
|
||||
])
|
||||
end
|
||||
|
||||
def convert(time, unit) do
|
||||
do_convert(time, String.downcase(unit))
|
||||
end
|
||||
|
||||
def known_unit?(val) do
|
||||
MapSet.member?(known_units(), String.downcase(val))
|
||||
end
|
||||
|
||||
defp do_convert(time, "days") do
|
||||
time * @days_seconds
|
||||
end
|
||||
|
||||
defp do_convert(time, "weeks") do
|
||||
time * @weeks_seconds
|
||||
end
|
||||
|
||||
defp do_convert(time, "months") do
|
||||
time * @months_seconds
|
||||
end
|
||||
|
||||
defp do_convert(time, "years") do
|
||||
time * @years_seconds
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue