如何为我的 /sidekiq 路由添加密码保护(即要求对 Sidekiq::Web 工具进行身份验证)?

63
我在我的Rails应用程序中使用Sidekiq。 默认情况下,只要在URL后面添加“/sidekiq”,任何人都可以访问Sidekiq。 我想对/sidekiq部分进行密码保护/身份验证。我该如何做到这一点?
9个回答

122
将以下内容放入你的Sidekiq初始化程序中。
require 'sidekiq'
require 'sidekiq/web'

Sidekiq::Web.use(Rack::Auth::Basic) do |user, password|
  # Protect against timing attacks:
  # - See https://codahale.com/a-lesson-in-timing-attacks/
  # - See https://thisdata.com/blog/timing-attacks-against-string-comparison/
  # - Use & (do not use &&) so that it doesn't short circuit.
  # - Use digests to stop length information leaking
  Rack::Utils.secure_compare(::Digest::SHA256.hexdigest(user), ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_USER"])) &
  Rack::Utils.secure_compare(::Digest::SHA256.hexdigest(password), ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_PASSWORD"]))
end

在路由文件中:

mount Sidekiq::Web => '/sidekiq'

4
顺便说一句,我将以下代码放入了我的 Gemfile 文件中,这样我就不必在初始化文件和 routes.rb 文件中手动引入 sidekiq 和 sidekiq/web 了:"gem 'sidekiq', require: ['sidekiq', 'sidekiq/web']" - odigity
4
至少在我使用 Rails 5 运行的应用中,不需要将 mount 调用包装在 authenticate 块中。 - stephen.hanson
2
如Sidekiq项目所述,您可能希望使用ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(user), ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_USER"])) & ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(password), ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_PASSWORD"]))来比较用户名和密码。 - Capripot
3
警告!使用 == 运算符会使应用程序容易受到时序攻击。攻击者可以根据请求处理时间逐个猜测每个字符,最终找出密码。请使用 ActiveSupport::SecurityUtils.secure_compareDevise.secure_compare 或类似功能的函数。 - Nowaker
6
不需要使用 authenticate 区块。如果您没有使用 Devise,在 Rails 6 上会出现错误。 - The Code
显示剩余2条评论

32

很抱歉晚到了,但是Sidekiq的维基推荐以下方法来支持Devise:

允许任何经过认证的User

# config/routes.rb
authenticate :user do
  mount Sidekiq::Web => '/sidekiq'
end

限制访问 User.admin?

# config/routes.rb
authenticate :user, lambda { |u| u.admin? } do
  mount Sidekiq::Web => '/sidekiq'
end

这篇维基文章还介绍了许多其他安全方案。

此代码已在Rails 5.1.3、Devise 4.3和Sidekiq 5.0上进行过测试。


1
谢谢你分享 Sidekiq wiki 上的最佳实践链接,Tom! - Kori John Roys
有没有文档可以展示如何使用 RSpec 进行测试? - user2012677
@tomAranda,你最终解决了如何进行rspec测试的问题吗? - user2012677
我已经为此编写了一个rspec,并且在不久的将来也没有计划。 - Tom Aranda

15
请参阅https://github.com/mperham/sidekiq/wiki/Monitoring中的“安全”部分。
Sidekiq::Web使用Rack::Protection保护您的应用程序免受典型的Web攻击(例如CSRFXSS等)。如果Rack::Protection发现您的请求未满足安全要求,它将使您的会话无效并引发Forbidden错误。其中一个可能的情况是您的应用程序在反向代理后面工作,但没有传递重要的标头(X-Forwarded-ForX-Forwarded-Proto)。有关该情况及其解决方案,请参见此文章问题#2560...

13

如果你正在自行编写自定义身份验证,那么你可以使用文档中此处引用的以下示例。

# lib/admin_constraint.rb
class AdminConstraint
  def matches?(request)
    return false unless request.session[:user_id]
    user = User.find request.session[:user_id]
    user && user.admin?
  end
end

# config/routes.rb
require 'sidekiq/web'
require 'admin_constraint'
mount Sidekiq::Web => '/sidekiq', :constraints => AdminConstraint.new

这个答案很有帮助,尽管我不确定我们是否可以避免每次请求sidekiq/stats时进行额外的查询,因为目前这种情况经常发生。 - csalmeida
在Rails 7中,我看到除非常量位于/app目录下,默认情况下无法找到它。也就是说,app/constraints/。 - Nick Res

11

如果您正在使用Devise(或其他基于Warden的身份验证),并且假设您在应用程序中有一个AdminUser模型,则可以执行此操作。

# config/routes.rb
# This defines the authentication constraint
constraint = lambda do |request|
               request.env['warden'].authenticate!({ scope: :admin_user })
             end

# This mounts the route using the constraint.
# You could use any other path to make it less obvious
constraints constraint do
  mount Sidekiq::Web => '/sidekiq'
end

我能把这个约束放在其他地方吗?我的意思是,而不是把它放在routes.rb中。 - Mayuresh Srivastava

7
接受的答案不错,但我认为可以更安全地实现,正如Sidekiq文档中提到的(在我发布后进行了编辑以展示正确的解决方案)。
为了保护您的应用免受时间攻击,请使用ActiveSupport::SecurityUtils.secure_compare
参见: 此外,请使用&(不要使用&&),以避免短路。
最后,请使用摘要来防止长度信息泄漏(在Active Support 5中secure_compare的默认值)。
因此,在初始化文件中,通常在Rails项目的config/initializers/sidekiq.rb中,根据您的Active Support / Rails版本编写以下内容。 Active Support 5+:由于Rails PR #24510,传递给secure_compare的参数默认经过Digest::SHA256.hexdigest
require 'active_support/security_utils'
require 'sidekiq'
require 'sidekiq/web'

Sidekiq::Web.use(Rack::Auth::Basic) do |user, password|
  # Protect against timing attacks:
  # - See https://codahale.com/a-lesson-in-timing-attacks/
  # - See https://thisdata.com/blog/timing-attacks-against-string-comparison/
  # - Use & (do not use &&) so that it doesn't short circuit.
  # - Use digests to stop length information leaking
  ActiveSupport::SecurityUtils.secure_compare(user, ENV["SIDEKIQ_ADMIN_USER"]) &
    ActiveSupport::SecurityUtils.secure_compare(password, ENV["SIDEKIQ_ADMIN_PASSWORD"])
end

Active Support 4:

require 'active_support/security_utils'
require 'sidekiq'
require 'sidekiq/web'

Sidekiq::Web.use(Rack::Auth::Basic) do |user, password|
  # Protect against timing attacks:
  # - See https://codahale.com/a-lesson-in-timing-attacks/
  # - See https://thisdata.com/blog/timing-attacks-against-string-comparison/
  # - Use & (do not use &&) so that it doesn't short circuit.
  # - Use digests to stop length information leaking
  ActiveSupport::SecurityUtils.secure_compare(
    ::Digest::SHA256.hexdigest(user),
    ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_ADMIN_USER"])
  ) &
    ActiveSupport::SecurityUtils.secure_compare(
      ::Digest::SHA256.hexdigest(password),
      ::Digest::SHA256.hexdigest(ENV["SIDEKIQ_ADMIN_PASSWORD"])
    )
end

1
如果您正在使用Sorcery进行身份验证,这里是如何使用Rails路由约束来保护特定的路由。

这里复制了巫术维基的内容以备不时之需:

本教程展示了如何使用Sorcery gem中的Rails路由约束。感谢@anthonator编写此教程!

首先,定义UserConstraint模块,该模块将用于所有约束:

module RouteConstraints::UserConstraint
  def current_user(request)
    User.find_by_id(request.session[:user_id])
  end
end

然后,定义了该模块后,您可以指定特定的约束类。在这些示例中,第一个路由仅在没有用户登录时起作用,第二个路由仅适用于已登录的管理员用户:

class RouteConstraints::NoUserRequiredConstraint
  include RouteConstraints::UserConstraint

  def matches?(request)
    !current_user(request).present?
  end
end

class RouteConstraints::AdminRequiredConstraint
  include RouteConstraints::UserConstraint

  def matches?(request)
    user = current_user(request)
    user.present? && user.is_admin?
  end
end

最后,您可以将约束条件添加到config/routes.rb中:
MyApp::Application.routes.draw do

  # other routes …

  root :to => 'admin#dashboard', :constraints => RouteConstraints::AdminRequiredConstraint.new
  root :to => 'home#welcome', :constraints => RouteConstraints::NoUserRequiredConstraint.new

end

0
另一个选择是添加类似CanCan的东西,并基于角色提供特殊访问权限。

0
能够使用设备的用户名和密码。
这对于API也适用。

#config/initializers/sidekiq.rb

require "sidekiq/web"

Sidekiq::Web.use Rack::Auth::Basic do |username, password|
    User.find_by(username: username)&.valid_password?(password)
end

#config/routes.rb

mount Sidekiq::Web => "/sidekiq"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接