How to store secret strings on a GEM? - ruby

I wrote a ruby gem to interact with a webservice. The gem contains the hardcoded value of client id and application secret.
So anyone else can use my data on his script and I will be charged for his requests. :(
On other hands it would be bad for the users if they have to create a new app in order to get a new personal application secret.
Any idea on how to store these info in a secure way?

While it might be OK to hardcode values in internal-only, early stage prototypes or temporary solution it is usually not advisable to hardcode values that normally change from application to application and most certainly not password or security tokens!
I would recommend that you made these values configurable in ways that people can easily set them via standard environment variables (very common in PaaS environments such as Heroku) or via initializers if need be. Exceptions are sane default values such as development database URLs (ex: redis' default 127.0.0.1:6379)
Example
module MyGem
class Configuration
attr_writer :client_id, :application_secret, :url
# Expects a MY_GEM_CLIENT_ID env var to be set in production
def client_id
#client_id ||= ENV.fetch('MY_GEM_CLIENT_ID', 'CLIENT_ID')
end
# Expects a MY_GEM_APPLICATION_SECRET env var to be set in production
def application_secret
#application_secret ||= ENV.fetch('MY_GEM_APPLICATION_SECRET', 'SECRET')
end
# ENV driven config but with working default
def url
#url ||= ENV.fetch('MY_GEM_URL', 'https://myservice.com/v1/api')
end
end
def self.configure(&block)
block.call(configuration)
end
def self.configuration
#configuration ||= Configuration.new
end
end
Initializer-based config of the code above
# config/initializers/my_gem.rb in Rails
# CustomConfigObject could be a Mixlib configuration object
MyGem.configure do |config|
config.client_id = CustomConfigObject.my_gem.client_id
config.application_sercret = CustomConfigObject.my_gem.application_secret
config.url = CustomConfigObject.my_gem.url
end
If you absolutely need to provide a default "sandbox" set of Client ID and Application Secret you can provide those values as defaults in the code above (the second argument to ENV.fetch()), but I'd recommend either adding a sandbox boolean setting that can be set to true/false and sent with API calls, at which point your API would process those as sandbox / dev calls or, preferably, add support for account-specific sandbox credentials.

Related

DRuby with Selenium WebDriver

I'm using distributed ruby so that I can save the selenium web-driver object in one script and use the same object in the next script when I run the clients I get an error indicating that #<Drb::DRbConnError: connection closed>.
Has anyone tried this or how do we overcome this issue?
Below are my scripts
Server.rb
require 'drb/drb'
# The URI for the server to connect to
URI="druby://localhost:8787"
# Allows sharing of variables between component runs
class TestScope
# class variable
##variables = {}
def save_variable(key, value)
##variables.store(key, value)
end
def get_variable(key)
return ##variables[key]
end
def get_size
return ##variables.size
end
end
# The object that handles requests on the server
FRONT_OBJECT=TestScope.new
DRb.start_service(URI, FRONT_OBJECT, safe_level: 1, verbose: true)
# Wait for the drb server thread to finish before exiting.
DRb.thread.join
Client1.rb
require 'drb/drb'
require 'selenium-webdriver'
# The URI to connect to
SERVER_URI="druby://localhost:8787"
# Start a local DRbServer to handle callbacks.
# Not necessary for this small example, but will be required
# as soon as we pass a non-marshallable object as an argument
# to a dRuby call.
# Note: this must be called at least once per process to take any effect.
# This is particularly important if your application forks.
DRb.start_service
test_scope = DRbObject.new_with_uri(SERVER_URI)
driver = Selenium::WebDriver::Driver.for :firefox
driver.navigate.to "http://www.google.com"
puts "Saving the driver object to the test scope"
test_scope.save_variable('driver', driver)
Client2.rb
require 'drb/drb'
require 'selenium-webdriver'
# The URI to connect to
SERVER_URI="druby://localhost:8787"
# Start a local DRbServer to handle callbacks.
# Not necessary for this small example, but will be required
# as soon as we pass a non-marshallable object as an argument
# to a dRuby call.
# Note: this must be called at least once per process to take any effect.
# This is particularly important if your application forks.
DRb.start_service
test_scope = DRbObject.new_with_uri(SERVER_URI)
puts "Fetching the driver object from the test scope"
driver1 = test_scope.get_variable('driver')
driver1.navigate.to "http://www.yahoo.com"
In order to share an object using DRb, the class must be defined in the dRb server as it allows an object in one Ruby process to invoke methods on an object in another Ruby process on the same or a different machine.
If there is a scenario where one needs to create an object on the dRb client and use that object in other DRb clients. We need to use ruby script runner and define object in the scriptrunner.rb so that multiple clients can use it.

Capybara/Selenium - force to reload Selenium Driver on each call

I have a solution to parse some data from a remote websites using Firefox(46) + Capybara + Selenium. I pass path_to_firefox_profile argument on initialize, which is some real Firefox profile folder.
The problem is that if I execute this code:
a = MyParser.new(profile_a)
a.parse_something
b = MyParser.new(profile_b)
b.parse_something
... the instance b will have profile A loaded despite I specified another profile.
But, if I run these 2 lines in a separate processes, I'll get what I want. So I assume one of them - either Capybara or Selenium - stores profiles settings once per ruby process, and won't change it on demand.
Are there are ideas how to change profile within the same process?
I tries to .quit Firefox, but it doesn't help, on visiting new URL Selenium opens another Firefox window with the exact same profile instead of new one.
class MyParser
def initialize(path_to_firefox_profile)
Capybara.register_driver(:selenium) do |app|
client = Selenium::WebDriver::Remote::Http::Default.new
client.timeout = 150
Capybara::Selenium::Driver.new(app,
profile: path_to_firefox_profile,
http_client: client)
end
end
def parse_something
# perform some parsings & return result
end
end
Capybara's register_driver registers the driver by the name you assign globally, and will use those drivers (by name) in its sessions. It will also automatically manage sessions in a manner designed for ease of use by people testing web-apps. The issue here is that a new session with the newly registered driver isn't being created. This is because you're not really using Capybara for what it was designed, and therefore need to pick up a bit more of the weight of session management. If you're ever going to have more than 2 of your MyParser objects around you probably should be creating a new session per MyParser instance you create and then using that session in all of the MyParser methods. Since you're using different driver settings per MyParser instance you should probably also be naming each driver registered differently.
class MyParser
def initialize(path_to_firefox_profile)
Capybara.register_driver(self.object_id.to_s) do |app|
client = Selenium::WebDriver::Remote::Http::Default.new
client.timeout = 150
Capybara::Selenium::Driver.new(app,
profile: path_to_firefox_profile,
http_client: client)
end
#session = Capybara::Session.new(self.object_id.to_s)
end
def parse_something
#session.visit("some url")
#session.find(...) # perform some parsings & return result
end
end
You will also need to handle cleaning up the session when you're done with each MyParser instance.

rails persist objects over requests in development mode

I am trying to interact with Matlab.Application.Single win32ole objects in my rails application. The problem I am running into is that while I am developing my application, each separate request reloads my win32ole objects so I loose the connection to my matlab orignal instances and new instances are made. Is there a way to persist live objects between requests in rails? or is there a way to reconnect to my Matlab.Application.Single instances?
In production mode I use module variables to store my connection between requests, but in development mode Module variables are reloaded every request.
here is a snippet of my code
require 'win32ole'
module Calculator
#engine2 = nil
#engine3 = nil
def self.engine2
if #engine2.nil?
#engine2 = WIN32OLE.new("Matlab.Application.Single")
#engine2.execute("run('setup_path.m')")
end
#engine2
end
def self.engine3
if #engine3.nil?
#engine3 = WIN32OLE.new("Matlab.Application.Single")
#engine3.execute("run('setup_path.m')")
end
#engine3
end
def self.load_CT_image(file)
Calculator.engine2.execute("spm_image('Init','#{file}')")
end
def self.load_MR_image(file)
Calculator.engine3.execute("spm_image('Init','#{file}')")
end
end
I am then able to use my code in my controllers like this:
Calculator.load_CT_image('Post_Incident_CT.hdr')
Calculator.load_MR_image('Post_Incident_MRI.hdr')
You can keep an app-wide object in a constant that won't be reset for every request. Add this to a new file in config/initializers/:
ENGINE_2 = WIN32OLE.new("Matlab.Application.Single")
You might also need to include the .execute("run('setup_path.m')") line here as well (I'm not familiar with WIN32OLE). You can then assign that object to your instance variables in your Calculator module (just replace the WIN32OLE.new("Matlab.Application.Single") call with ENGINE_2, or simply refer to them directly.
I know this is beyond the scope of your question, but you have a lot of duplicated code here, and you might want to think about creating a class or module to manage your Matlab instances -- spinning up new ones as needed, and shutting down old ones that are no longer in use.

Subclass Rack::Throttle

i am reading about Rack::Throttle and i want to change the default client identifier from an IP to somethng else. The documentation says it can be done
The rate-limiting counters stored and maintained by Rack::Throttle are
keyed to unique HTTP clients.
By default, HTTP clients are uniquely identified by their IP address
as returned by Rack::Request#ip. If you wish to instead use a more
granular, application-specific identifier such as a session key or a
user account name, you need only subclass a throttling strategy
implementation and override the #client_identifier method.
I have no clue where to add that in, here is my current subclass for another method. Does anybody know how to do this? https://github.com/datagraph/rack-throttle
module Rack
module Throttle
class DailyRequests < Daily
def allowed?(request)
## Insert rules
super request
end
end
class HourlyRequests < Hourly
def allowed?(request)
## Insert rules
super request
end
end
class RequestInterval < Interval
def allowed?(request)
## Insert rules
super request
end
end
end
end
You should subclass one of the existing rack-throttle classes (probably either Rack::Throttle::Interval or Rack::Throttle::TimeWindow, whichever one more closely aligns with your needs) and override the #client_identifier method.
#client_identifier is passed one argument, request, which is a Rack::Request instance containing information passed in the incoming HTTP request and can be used to get information such as HTTP headers, cookies, path, and possibly other info depending on your app. The default implementation looks like this:
# #param [Rack::Request] request
# #return [String]
def client_identifier(request)
request.ip.to_s
end
Here's an example of subclassing Rack::Throttle::Interval to match requests on a query parameter such as ?user_id=<id>:
class UserThrottle < Rack::Throttle::Interval
def client_identifier(request)
request['user_id']
end
end
which you could use in a Rack application with:
use UserThrottle, :min => 100
Notice you can still pass options like :min to the Rack use statement, since it is just subclassing the existing throttle classes. And adopting this in a Rails app would just involve calling use in your application.rb file (see Rails on Rack).

How can I password-protect my /sidekiq route (i.e. require authentication for the Sidekiq::Web tool)?

I am using sidekiq in my rails application.
By Default, Sidekiq can be accessed by anybody by appending "/sidekiq" after the url.
I want to password protect / authenticate only the sidekiq part. How can i do that?
Put the following into your sidekiq initializer
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
And in the routes file:
mount Sidekiq::Web => '/sidekiq'
Sorry to late to the party, but Sidekiq's wiki recommends the following for Devise:
To allow any authenticated User:
# config/routes.rb
authenticate :user do
mount Sidekiq::Web => '/sidekiq'
end
To restrict access to User.admin?
# config/routes.rb
authenticate :user, lambda { |u| u.admin? } do
mount Sidekiq::Web => '/sidekiq'
end
This wiki post also has many other security schemes.
This was tested using Rails 5.1.3, Devise 4.3 and Sidekiq 5.0
See "Security" under https://github.com/mperham/sidekiq/wiki/Monitoring
Sidekiq::Web uses Rack::Protection to protect your application against typical web attacks (such as CSRF, XSS, etc). Rack::Protection would invalidate your session and raise Forbidden error if it finds that your request doesn't satisfy security requirements. One of the possible situations is having your application working behind a reverse proxy and not passing important headers to it (X-Forwarded-For,X-Forwarded-Proto). Such situation and solution could be found in this article and issue #2560...
If you're using Devise (or other Warden-based authentication), you can do this, supposing you have an AdminUser model in your app.
# 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
If you are rolling your own custom authentication, then you can use the below example which is referenced in the docs 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
The accepted answer is good, but I think that it can be implemented more securely, as Sidekiq documentation mentions (it got edited to demonstrate the right solution after I posted).
To protect your app against timing attacks, use ActiveSupport::SecurityUtils.secure_compare.
See https://codahale.com/a-lesson-in-timing-attacks/
See https://thisdata.com/blog/timing-attacks-against-string-comparison/
Also, use & (do not use &&) so that it doesn't short circuit.
And finally, use digests to stop length information leaking (default of secure_compare in Active Support 5).
So, in an initializer file, typically in config/initializers/sidekiq.rb in Rails projects, depending of your version of Active Support/Rails, write the following.
Active Support 5+: Thanks to Rails PR #24510, parameters passed to secure_compare are going through Digest::SHA256.hexdigest by default.
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
If you're using Sorcery for authentication, here's how to use Rails routes constraints to protect certain routes.
Copied here from the sorcery wiki for redundancy:
This tutorial shows how to use Rails routes constraints with Sorcery gem. Thanks to #anthonator for writing it!
First, define UserConstraint module that will be used for all constraints:
module RouteConstraints::UserConstraint
def current_user(request)
User.find_by_id(request.session[:user_id])
end
end
Then, having that module defined, you can specify specific constraint classes. In these examples, first route will work only if there's no user logged in, the second will work only for logged user who is an admin:
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
Finally, you can add the constraints to the 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
Another option would be to add something like CanCan and special access based on roles.

Resources