filter snmp communities from log
This commit is contained in:
parent
aed15dd6a1
commit
1f6151b5a8
6 changed files with 266 additions and 4 deletions
|
|
@ -54,7 +54,20 @@ config :mime, :types, %{
|
||||||
}
|
}
|
||||||
|
|
||||||
# Filter sensitive parameters from logs
|
# Filter sensitive parameters from logs
|
||||||
config :phoenix, :filter_parameters, ["password", "snmp_community", "secret", "token", "api_key"]
|
# These parameters will be replaced with "[FILTERED]" in Phoenix logs and error reports
|
||||||
|
config :phoenix, :filter_parameters, [
|
||||||
|
"password",
|
||||||
|
"snmp_community",
|
||||||
|
"community",
|
||||||
|
"secret",
|
||||||
|
"token",
|
||||||
|
"api_key",
|
||||||
|
# SNMPv3 credentials (for future use)
|
||||||
|
"auth_password",
|
||||||
|
"priv_password",
|
||||||
|
"auth_pass",
|
||||||
|
"priv_pass"
|
||||||
|
]
|
||||||
|
|
||||||
# Use Jason for JSON parsing in Phoenix
|
# Use Jason for JSON parsing in Phoenix
|
||||||
config :phoenix, :json_library, Jason
|
config :phoenix, :json_library, Jason
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
defmodule Towerops.HoneybadgerFilter do
|
defmodule Towerops.HoneybadgerFilter do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Custom Honeybadger filter to exclude noisy errors during deployments.
|
Custom Honeybadger filter to exclude noisy errors during deployments
|
||||||
|
and filter sensitive SNMP credentials from error reports.
|
||||||
|
|
||||||
Filters out OTP supervisor/gen_server errors that occur during normal
|
Filters out OTP supervisor/gen_server errors that occur during normal
|
||||||
node shutdown/restart.
|
node shutdown/restart.
|
||||||
|
|
@ -15,6 +16,20 @@ defmodule Towerops.HoneybadgerFilter do
|
||||||
"Elixir.PollerSupervisor"
|
"Elixir.PollerSupervisor"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Sensitive keys that should be filtered from error reports
|
||||||
|
@sensitive_keys [
|
||||||
|
"password",
|
||||||
|
"snmp_community",
|
||||||
|
"community",
|
||||||
|
"secret",
|
||||||
|
"token",
|
||||||
|
"api_key",
|
||||||
|
"auth_password",
|
||||||
|
"priv_password",
|
||||||
|
"auth_pass",
|
||||||
|
"priv_pass"
|
||||||
|
]
|
||||||
|
|
||||||
@impl Honeybadger.Filter
|
@impl Honeybadger.Filter
|
||||||
def filter_context(context) do
|
def filter_context(context) do
|
||||||
cond do
|
cond do
|
||||||
|
|
@ -27,10 +42,25 @@ defmodule Towerops.HoneybadgerFilter do
|
||||||
nil
|
nil
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
super(context)
|
context
|
||||||
|
|> super()
|
||||||
|
|> filter_sensitive_data()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl Honeybadger.Filter
|
||||||
|
def filter_params(params) when is_map(params) do
|
||||||
|
Map.new(params, fn {key, value} ->
|
||||||
|
if should_filter?(key) do
|
||||||
|
{key, "[FILTERED]"}
|
||||||
|
else
|
||||||
|
{key, filter_params(value)}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_params(params), do: params
|
||||||
|
|
||||||
# Detect anonymous gen_server termination during shutdown
|
# Detect anonymous gen_server termination during shutdown
|
||||||
defp otp_gen_server_shutdown?(context) do
|
defp otp_gen_server_shutdown?(context) do
|
||||||
domain = get_in(context, [:domain])
|
domain = get_in(context, [:domain])
|
||||||
|
|
@ -38,4 +68,29 @@ defmodule Towerops.HoneybadgerFilter do
|
||||||
|
|
||||||
domain == ["otp"] and match?(["gen_server", "error_info", _], mfa)
|
domain == ["otp"] and match?(["gen_server", "error_info", _], mfa)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Filter sensitive data from context
|
||||||
|
defp filter_sensitive_data(nil), do: nil
|
||||||
|
|
||||||
|
defp filter_sensitive_data(context) when is_map(context) do
|
||||||
|
context
|
||||||
|
|> Map.update(:cgi_data, %{}, &filter_params/1)
|
||||||
|
|> Map.update(:params, %{}, &filter_params/1)
|
||||||
|
|> Map.update(:session, %{}, &filter_params/1)
|
||||||
|
|> Map.update(:context, %{}, &filter_params/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp filter_sensitive_data(context), do: context
|
||||||
|
|
||||||
|
# Check if a key should be filtered
|
||||||
|
defp should_filter?(key) when is_binary(key) do
|
||||||
|
key_lower = String.downcase(key)
|
||||||
|
Enum.any?(@sensitive_keys, &String.contains?(key_lower, &1))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp should_filter?(key) when is_atom(key) do
|
||||||
|
should_filter?(Atom.to_string(key))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp should_filter?(_), do: false
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,17 @@
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<%= unless @invitation do %>
|
||||||
|
<.input
|
||||||
|
name="user[organization_name]"
|
||||||
|
value=""
|
||||||
|
type="text"
|
||||||
|
label="Organization Name"
|
||||||
|
placeholder="My Company"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<.button phx-disable-with="Creating account..." class="w-full" variant="primary">
|
<.button phx-disable-with="Creating account..." class="w-full" variant="primary">
|
||||||
{if @invitation, do: "Accept invitation and create account", else: "Create an account"}
|
{if @invitation, do: "Accept invitation and create account", else: "Create an account"}
|
||||||
</.button>
|
</.button>
|
||||||
|
|
|
||||||
159
test/towerops/honeybadger_filter_test.exs
Normal file
159
test/towerops/honeybadger_filter_test.exs
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
defmodule Towerops.HoneybadgerFilterTest do
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
alias Towerops.HoneybadgerFilter
|
||||||
|
|
||||||
|
describe "filter_params/1" do
|
||||||
|
test "filters SNMP community strings" do
|
||||||
|
params = %{
|
||||||
|
"name" => "Device 1",
|
||||||
|
"snmp_community" => "public",
|
||||||
|
"ip_address" => "192.168.1.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = HoneybadgerFilter.filter_params(params)
|
||||||
|
|
||||||
|
assert filtered["name"] == "Device 1"
|
||||||
|
assert filtered["snmp_community"] == "[FILTERED]"
|
||||||
|
assert filtered["ip_address"] == "192.168.1.1"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "filters community strings with just 'community' key" do
|
||||||
|
params = %{"community" => "secret123", "device_id" => "abc"}
|
||||||
|
|
||||||
|
filtered = HoneybadgerFilter.filter_params(params)
|
||||||
|
|
||||||
|
assert filtered["community"] == "[FILTERED]"
|
||||||
|
assert filtered["device_id"] == "abc"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "filters passwords" do
|
||||||
|
params = %{"email" => "user@example.com", "password" => "secret"}
|
||||||
|
|
||||||
|
filtered = HoneybadgerFilter.filter_params(params)
|
||||||
|
|
||||||
|
assert filtered["email"] == "user@example.com"
|
||||||
|
assert filtered["password"] == "[FILTERED]"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "filters SNMPv3 credentials" do
|
||||||
|
params = %{
|
||||||
|
"snmp_version" => "3",
|
||||||
|
"auth_password" => "authpass",
|
||||||
|
"priv_password" => "privpass",
|
||||||
|
"username" => "admin"
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = HoneybadgerFilter.filter_params(params)
|
||||||
|
|
||||||
|
assert filtered["snmp_version"] == "3"
|
||||||
|
assert filtered["auth_password"] == "[FILTERED]"
|
||||||
|
assert filtered["priv_password"] == "[FILTERED]"
|
||||||
|
assert filtered["username"] == "admin"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "filters API keys and tokens" do
|
||||||
|
params = %{
|
||||||
|
"api_key" => "key123",
|
||||||
|
"token" => "token456",
|
||||||
|
"secret" => "secret789"
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = HoneybadgerFilter.filter_params(params)
|
||||||
|
|
||||||
|
assert filtered["api_key"] == "[FILTERED]"
|
||||||
|
assert filtered["token"] == "[FILTERED]"
|
||||||
|
assert filtered["secret"] == "[FILTERED]"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "filters nested params" do
|
||||||
|
params = %{
|
||||||
|
"device" => %{
|
||||||
|
"name" => "Router",
|
||||||
|
"snmp_community" => "private",
|
||||||
|
"ip_address" => "10.0.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = HoneybadgerFilter.filter_params(params)
|
||||||
|
|
||||||
|
assert filtered["device"]["name"] == "Router"
|
||||||
|
assert filtered["device"]["snmp_community"] == "[FILTERED]"
|
||||||
|
assert filtered["device"]["ip_address"] == "10.0.0.1"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "filters atom keys" do
|
||||||
|
params = %{
|
||||||
|
name: "Device",
|
||||||
|
snmp_community: "public",
|
||||||
|
ip_address: "192.168.1.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = HoneybadgerFilter.filter_params(params)
|
||||||
|
|
||||||
|
assert filtered[:name] == "Device"
|
||||||
|
assert filtered[:snmp_community] == "[FILTERED]"
|
||||||
|
assert filtered[:ip_address] == "192.168.1.1"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles case-insensitive filtering" do
|
||||||
|
params = %{
|
||||||
|
"SNMP_COMMUNITY" => "public",
|
||||||
|
"SnmpCommunity" => "private",
|
||||||
|
"API_KEY" => "secret"
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = HoneybadgerFilter.filter_params(params)
|
||||||
|
|
||||||
|
assert filtered["SNMP_COMMUNITY"] == "[FILTERED]"
|
||||||
|
assert filtered["SnmpCommunity"] == "[FILTERED]"
|
||||||
|
assert filtered["API_KEY"] == "[FILTERED]"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not filter non-sensitive data" do
|
||||||
|
params = %{
|
||||||
|
"name" => "Device",
|
||||||
|
"ip_address" => "192.168.1.1",
|
||||||
|
"description" => "Main router"
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = HoneybadgerFilter.filter_params(params)
|
||||||
|
|
||||||
|
assert filtered == params
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "filter_context/1" do
|
||||||
|
test "filters sensitive data from context params" do
|
||||||
|
context = %{
|
||||||
|
params: %{
|
||||||
|
"device" => %{"snmp_community" => "public", "name" => "Router"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = HoneybadgerFilter.filter_context(context)
|
||||||
|
|
||||||
|
assert filtered.params["device"]["name"] == "Router"
|
||||||
|
assert filtered.params["device"]["snmp_community"] == "[FILTERED]"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "filters sensitive data from cgi_data" do
|
||||||
|
context = %{
|
||||||
|
cgi_data: %{"auth_token" => "Bearer token123", "user_agent" => "Mozilla/5.0"}
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = HoneybadgerFilter.filter_context(context)
|
||||||
|
|
||||||
|
assert filtered.cgi_data["auth_token"] == "[FILTERED]"
|
||||||
|
assert filtered.cgi_data["user_agent"] == "Mozilla/5.0"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "filters ignored registered names" do
|
||||||
|
context = %{registered_name: "memsup"}
|
||||||
|
|
||||||
|
filtered = HoneybadgerFilter.filter_context(context)
|
||||||
|
|
||||||
|
assert filtered == nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -57,5 +57,26 @@ defmodule ToweropsWeb.UserRegistrationControllerTest do
|
||||||
assert response =~ "Register"
|
assert response =~ "Register"
|
||||||
assert response =~ "can't be blank"
|
assert response =~ "can't be blank"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "creates organization with provided name", %{conn: conn} do
|
||||||
|
email = unique_user_email()
|
||||||
|
|
||||||
|
conn =
|
||||||
|
post(conn, ~p"/users/register", %{
|
||||||
|
"user" => %{
|
||||||
|
"email" => email,
|
||||||
|
"password" => valid_user_password(),
|
||||||
|
"organization_name" => "Acme Corp"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert get_session(conn, :user_token)
|
||||||
|
assert redirected_to(conn) == ~p"/devices"
|
||||||
|
|
||||||
|
# Verify organization was created with the provided name
|
||||||
|
user = Towerops.Accounts.get_user_by_email(email)
|
||||||
|
[org | _] = Towerops.Organizations.list_user_organizations(user.id)
|
||||||
|
assert org.name == "Acme Corp"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,9 @@ defmodule ToweropsWeb.UserRegistrationHTMLTest do
|
||||||
render_to_string(ToweropsWeb.UserRegistrationHTML, "new", "html",
|
render_to_string(ToweropsWeb.UserRegistrationHTML, "new", "html",
|
||||||
flash: %{},
|
flash: %{},
|
||||||
current_scope: nil,
|
current_scope: nil,
|
||||||
form: form
|
form: form,
|
||||||
|
invitation: nil,
|
||||||
|
invitation_token: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
assert html =~ "Register for an account"
|
assert html =~ "Register for an account"
|
||||||
|
|
@ -24,6 +26,7 @@ defmodule ToweropsWeb.UserRegistrationHTMLTest do
|
||||||
assert html =~ "Log in"
|
assert html =~ "Log in"
|
||||||
assert html =~ "Create an account"
|
assert html =~ "Create an account"
|
||||||
assert html =~ "Password"
|
assert html =~ "Password"
|
||||||
|
assert html =~ "Organization Name"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue