【问题标题】:How can I password-protect my /sidekiq route (i.e. require authentication for the Sidekiq::Web tool)?如何使用密码保护我的 /sidekiq 路由(即需要对 Sidekiq::Web 工具进行身份验证)?
【发布时间】:2012-08-29 05:27:35
【问题描述】:

我在我的 rails 应用程序中使用 sidekiq。 默认情况下,任何人都可以通过在 URL 后附加“/sidekiq”来访问 Sidekiq。 我只想对 sidekiq 部分进行密码保护/身份验证。我该怎么做?

【问题讨论】:

    标签: ruby ruby-on-rails-3 redis rescue sidekiq


    【解决方案1】:

    将以下内容放入您的 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'
    

    【讨论】:

    • 顺便说一句-我将以下内容放在我的 Gemfile 中,这样我就不必在初始化程序和 routes.rb 文件中手动要求 sidekiq 和 sidekiq/web:“gem 'sidekiq', require: ['sidekiq', 'sidekiq/web']"
    • 至少在我运行 Rails 5 的应用程序中,我没有必要用 authenticate 块包装 mount 调用。
    • 如 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"])) 来比较用户名和密码。
    • 警告!使用== 运算符会使应用程序暴露于定时攻击。任何攻击者最终都可以通过根据请求处理时间逐个猜测每个字符来找出密码是什么。使用ActiveSupport::SecurityUtils.secure_compareDevise.secure_compare 或类似名称。
    • 你不需要authenticate 块。如果您没有使用设计,它会在 rails 6 上出现错误
    【解决方案2】:

    很抱歉迟到了,但Sidekiq's wiki 为 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
    

    This wiki post 还有很多其他的安全方案。

    这是使用 Rails 5.1.3、Devise 4.3 和 Sidekiq 5.0 测试的

    【讨论】:

    • 感谢汤姆,Sidekiq wiki 上的良好实践链接!
    • 是否有任何文档显示如何对 RSpec 进行测试?
    • @tomAranda 你们都知道如何进行 rspec 测试了吗?
    • 我已经为此编写了一个 rspec,但我不打算在不久的将来。
    【解决方案3】:

    参见https://github.com/mperham/sidekiq/wiki/Monitoring下的“安全”

    Sidekiq::Web 使用Rack::Protection 保护您的应用程序免受典型的网络攻击(例如CSRFXSS 等)。如果发现您的请求不满足安全要求,Rack::Protection 将使您的会话无效并引发Forbidden 错误。一种可能的情况是让您的应用程序在反向代理后面工作,而不是将重要的标头传递给它(X-Forwarded-ForX-Forwarded-Proto)。这种情况和解决办法可以在in this articleissue #2560找到...

    【讨论】:

      【解决方案4】:

      如果您使用的是 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
      【解决方案5】:

      如果您正在滚动自己的自定义身份验证,则可以使用文档here 中引用的以下示例。

      # 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
      

      【讨论】:

        【解决方案6】:

        接受的答案很好,但我认为它可以更安全地实施,如Sidekiq documentation mentions(在我发布后对其进行了编辑以展示正确的解决方案)。

        要保护您的应用免受计时攻击,请使用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
        

        主动支持 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
        

        【讨论】:

          【解决方案7】:

          如果您使用Sorcery 进行身份验证,这里是how to use Rails routes constraints 来保护某些路由。


          为了冗余,从巫术维基复制到这里:

          本教程展示了如何通过 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
          

          【讨论】:

            【解决方案8】:

            另一种选择是添加 CanCan 之类的内容和基于角色的特殊访问权限。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2017-04-26
              • 2017-01-10
              • 1970-01-01
              • 2018-06-18
              • 2021-12-16
              • 1970-01-01
              • 2020-05-10
              相关资源
              最近更新 更多