RmqUtilsController updates

pull/1231/head
Rick Carlino 2019-06-11 11:14:58 -05:00
parent f3f39d6758
commit cf5f6f020f
2 changed files with 77 additions and 47 deletions

View File

@ -25,7 +25,7 @@ module Api
# The only valid format for AMQP / MQTT topics.
# Prevents a whole host of abuse / security issues.
DEVICE_SPECIFIC_CHANNELS = \
DEVICE_SPECIFIC_CHANNELS =
Regexp.new("bot\\.device_\\d*\\.(#{BOT_CHANNELS})")
PUBLIC_BROADCAST = "public_broadcast"
PUBLIC_CHANNELS = ["", ".*", ".#"].map { |x| PUBLIC_BROADCAST + x }
@ -34,6 +34,11 @@ module Api
VHOST = ENV.fetch("MQTT_VHOST") { "/" }
RESOURCES = ["queue", "exchange"]
PERMISSIONS = ["configure", "read", "write"]
class PasswordFailure < Exception; end
rescue_from PasswordFailure, with: :report_suspicious_behavior
skip_before_action :check_fbos_version, except: []
skip_before_action :authenticate_user!, except: []
@ -102,9 +107,24 @@ module Api
end
def authenticate_admin
correct_pw = password_param == ENV.fetch("ADMIN_PASSWORD")
correct_pw = password_param == admin_password
ok = admin? && correct_pw
ok ? allow("management", "administrator") : deny
if ok
allow("management", "administrator")
else
raise PasswordFailure
end
end
def admin_password
@admin_password ||= ENV.fetch("ADMIN_PASSWORD")
rescue KeyError
raise PasswordFailure
end
def report_suspicious_behavior
Rollbar.error("Failed password attempt on RMQ: " + password_param)
deny
end
def deny

View File

@ -5,22 +5,34 @@ describe Api::RmqUtilsController do
let :credentials do
pass = "password123"
user = FactoryBot.create(:user,
password: pass,
password: pass,
password_confirmation: pass)
token = Auth::CreateToken
.run!(email: user.email,
password: pass,
fbos_version: Gem::Version.new("99.99.99"))[:token].encoded
.run!(email: user.email,
password: pass,
fbos_version: Gem::Version.new("99.99.99"))[:token].encoded
{ username: "device_#{user.device.id}",
password: token }
end
it "reports people trying to use ADMIN_PASSWORD on non-local servers" do
k = "ADMIN_PASSWORD"
old_pw = ENV[k]
ENV.delete(k)
post :user_action, params: { username: "admin", password: old_pw }
expect(response.status).to eq(403)
expect(response.body).not_to include("allow")
ENV[k] = old_pw
end
it "allows admins to do anything" do
all = \
all =
[:user_action, :vhost_action, :resource_action, :topic_action]
all.map do |action|
post action, params: { username: "admin",
password: ENV.fetch("ADMIN_PASSWORD") }
password: ENV.fetch("ADMIN_PASSWORD") }
expect(response.status).to eq(200)
expect(response.body).to include("allow")
end
@ -28,7 +40,7 @@ describe Api::RmqUtilsController do
it "denies public_broadcast write access to non-admin users" do
routing_key = Api::RmqUtilsController::PUBLIC_CHANNELS.sample
permission = ["write", "configure"].sample
permission = ["write", "configure"].sample
p = credentials.merge(routing_key: routing_key, permission: permission)
post :topic_action, params: p
expect(response.body).to eq("deny")
@ -37,7 +49,7 @@ describe Api::RmqUtilsController do
it "allows public_broadcast read access to non-admin users" do
routing_key = Api::RmqUtilsController::PUBLIC_CHANNELS.sample
permission = "read"
permission = "read"
p = credentials.merge(routing_key: routing_key, permission: permission)
post :topic_action, params: p
expect(response.body).to eq("allow")
@ -65,7 +77,7 @@ describe Api::RmqUtilsController do
end
it "always denies guest users" do
no_no_no = \
no_no_no =
{ username: "guest", # RabbitMQ Default user.
password: "guest" } # RabbitMQ Default user.
post :user_action, params: no_no_no
@ -75,7 +87,7 @@ describe Api::RmqUtilsController do
it "`allow`s admin users when ADMIN_PASSWORD is provided" do
admin_params = { username: "admin",
password: ENV.fetch("ADMIN_PASSWORD") }
password: ENV.fetch("ADMIN_PASSWORD") }
post :user_action, params: admin_params
expect(response.body).to include("allow")
expect(response.status).to eq(200)
@ -126,16 +138,16 @@ describe Api::RmqUtilsController do
it "allows RMQ resource usage" do
post :resource_action, params: credentials.merge({
resource: Api::RmqUtilsController::RESOURCES.sample,
permission: Api::RmqUtilsController::PERMISSIONS.sample,
})
resource: Api::RmqUtilsController::RESOURCES.sample,
permission: Api::RmqUtilsController::PERMISSIONS.sample,
})
expect(response.status).to eq(200)
expect(response.body).to include("allow")
end
it "denies RMQ resource usage" do
post :resource_action, params: credentials.merge({ resource: "something_else",
permission: "something_else" })
post :resource_action, params: credentials.merge({ resource: "something_else",
permission: "something_else" })
expect(response.status).to eq(403)
expect(response.body).to include("deny")
end
@ -146,35 +158,33 @@ describe Api::RmqUtilsController do
it "validates topic names" do
r = Api::RmqUtilsController::DEVICE_SPECIFIC_CHANNELS
[ "*",
"#",
"foo",
"foo.*",
"bot.*",
random_channel,
random_channel(".nope"),
random_channel(".status_v3.*"),
random_channel(".status_v3"),
].map { |x| expect(x.match(r)).to be(nil) }
["*",
"#",
"foo",
"foo.*",
"bot.*",
random_channel,
random_channel(".nope"),
random_channel(".status_v3.*"),
random_channel(".status_v3")].map { |x| expect(x.match(r)).to be(nil) }
[ ".from_api.*",
".from_api",
".from_clients.*",
".from_clients",
".from_device.*",
".from_device",
".logs.*",
".logs",
".nerves_hub.*",
".nerves_hub",
".resources_v0.*",
".resources_v0",
".status.*",
".status",
".sync.*",
".sync",
".status_v8.*",
".status_v8",
].map { |x| expect(random_channel(x).match(r)).to be }
[".from_api.*",
".from_api",
".from_clients.*",
".from_clients",
".from_device.*",
".from_device",
".logs.*",
".logs",
".nerves_hub.*",
".nerves_hub",
".resources_v0.*",
".resources_v0",
".status.*",
".status",
".sync.*",
".sync",
".status_v8.*",
".status_v8"].map { |x| expect(random_channel(x).match(r)).to be }
end
end