Rails Tricks
Archive
04 Jul
Automate some of your security - Rails Tricks Issue 13
Hi there,This week, I will show you how you can automate some of the security necessities of a Rails application. If you follow this guide, you will be safe from one of the OWASP Top 10 security issues(A9-Using Components with Known Vulnerabilities) and lower the chances of having other vulnerabilities in your codebase. Let’s get into it.
Using Components with Known Vulnerabilities is in the OWASP Top 10, but automating a notification about gems with known vulnerabilities is very easy. The bundler-audit gem covers us there with the bundle-audit command:
[~/] bundle-audit --help Commands: bundler-audit check [DIR] # Checks the Gemfile.lock for insecure dependencies bundler-audit download # Downloads ruby-advisory-db bundler-audit help [COMMAND] # Describe available commands or one specific command bundler-audit stats # Prints ruby-advisory-db stats bundler-audit update # Updates the ruby-advisory-db bundler-audit version # Prints the bundler-audit version
If you put a check for bundle audit --update to your CI workflow, it will check your app for vulnerable dependencies and your pipeline will fail. Additionally, if you use yarn to manage your javascript dependencies, you can use yarn audit to check your dependencies for any known vulnerability.
Here is an example GitHub Action file to do this:
# .github/workflows/bundle-audit.yml
name: Bundle Audit
on:
pull_request:
schedule:
- cron: "0 0 * * *"
jobs:
base:
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2
bundler-cache: true
- name: Install bundler-audit
run: gem install bundler-audit
- name: Check dependencies with known vulnerabilities
run: bundle-audit --update
- name: Check javascript dependencies
run: yarn auditThe above action runs bundle audit and yarn audit on every pull request and at midnight every day. You might need to adjust the Ruby version above to the one you are on.
Another low-hanging fruit to improve the security posture of a Ruby on Rails application is to set up static code analyses for potential security issues. There are two gems to help with this: brakeman and spektr(DISCLAIMER: I am the author of this gem). These gems analyze your code for potentially vulnerable code and can help to find SQL injections, XSS, and quite a few other issues.
Using on CI brakeman is more ideal, because it supports ignoring false positives out of the box. Spektr is targeted more towards security professionals running it on a codebase during an assessment.
Here is an example GitHub Actions file to run brakeman on your codebase on every pull request and once every day:
# .github/workflows/brakeman-scan.yml
name: Brakeman Scan
on:
- pull_request:
- schedule:
- cron: "0 0 * * *"
jobs:
base:
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2
bundler-cache: true
- name: Install brakeman
run: gem install brakeman
- name: Static code analyses for security
run: brakemanThat’s it for this week.
27 Jun
Customizing the Rails console
Hi there,
I started to deploy my latest pet project with MRSK and I realized that I miss the customizations from my Rails console on the server. And I thought it might be useful to share how I customize my Rails console, so here we go.
The default Rails console is built on top of IRB, so to customize it, we need to look at IRBs documentation. The docs say that we can either create a global .irbrc in the home directory of the user, or have a project specific one in the project directory, but the home directory one will have a presedence. In my example, I keep it in the project, so I can copy it with docker to the containers when I deploy.
This .irbrc file is a evaluated as ruby, so to do our customizations, we can use our favorite language. The first line in my config is to disable autocompletion because I dislike it:
IRB.conf[:USE_AUTOCOMPLETE] = false
For my dockerized project, I also disable IRB history, since the containers are short-lived, and the history won’t be kept long and it also prevents the exception on the console exit when it fails to write to the file.
IRB.conf[:SAVE_HISTORY] = false if Rails.env.production?
The next thing I have in my file is a prompt customization. I use the pastel gem to colorize the prompt based on the environment:
require "pastel"
pastel = Pastel.new
prompt = case Rails.env
when "development"
pastel.black.on_green.bold.dim Rails.env
when "production"
pastel.white.on_red.bold.dim Rails.env
end
# defining custom prompt
IRB.conf[:PROMPT][:CUSTOM] = { # name of prompt mode
:PROMPT_I => "#{prompt}>> ", # simple prompt
:PROMPT_S => "#{prompt}* ", # prompt for continuated strings
:PROMPT_C => "#{prompt}? ", # prompt for continuated statement
:RETURN => " => %s\n" # format to return value
}
IRB.conf[:PROMPT_MODE] = :CUSTOMAs you can see, I don’t do anything fancy in my prompt, just outputting the Rails environment on a green or red background based on the environment. You can get it fancier by using the following special characters in the prompt:
%N # command name which is running
%m # to_s of main object (self)
%M # inspect of main object (self)
%l # type of string(", ', /, ]), `]' is inner %w[...]
%NNi # indent level. NN is digits and means as same as printf("%NNd").
# It can be omitted
%NNn # line number.
%% # %That’s it for this week.
20 Jun
Offline Ruby and Rails documentation
The vacation season is starting, and it can be helpful to have access without the internet to the documentation of Ruby, Rails, and the gems you use. If you are going on a trip and taking your laptop, you can prepare them in advance by following this little guide.
Let’s start with Ruby, that’s the easiest. To browse the API documentation offline, you can download a prebuilt copy from https://ruby-doc.org/downloads/. Extract the downloaded archive, open index.html and you can browse the documentation.
For Rails, you can generate the Rails Guides for local browsing. You need to clone the Rails repository, install some dependencies and run a rake command to generate the guides:
[~/] git clone https://github.com/rails/rails.git [~/] cd rails/guides [~/rails/guides] bundle [~/rails/guides] rake guides:generate:html
When it is done, you will have the files in the output directory. The above will generate the guides for edge Rails, if you want to see the guides for a specific version, you need to checkout that branch. For instance for Rails 7.0:
[~/rails/guides] git checkout 7-0-stable [~/rails/guides] rake guides:generate:html
You would probably also want to have the API documentation at hand. To generate that, you need to run the following rake command:
[~/rails] rake rdoc
Then open doc/rdoc/index.html in a browser.
For the gems, you have multiple options:
RDoc
First of all, you should make sure you have the rdoc files generated for the gems you have installed on your system. For that you can run:
gem rdoc --all
Once the docs are generated, you need to install the rubygems-server gem and start the server:
gem install rubygems-server gem server
YARD
YARD is another documentation tool for ruby gem. To run the yard server, you need to install the gem and start the server:
gem install yard yard gems
RI
If you don’t want to leave your terminal, you can also use ri to browse the documentation. You can run ri Array to view the documentation of the Array class, or ri ActionController::Base to view the rdoc docs for ActionController::Base.
That’s it for the week!
13 Jun
Active Record Transactions - Rails Tricks Issue 10
This week we will look into database transactions.
First of all, let me try to explain what they are. A database transaction is a unit of work that encapsulates dependencies and is executed either completely or rolled back to the initial state. For instance, there is double-entry accounting, where you always have a credit and debit record for a transaction, so your accounts stay in balance, and you never want to end up in a situation where you record only one side of a transaction.
If you were working on an app that helps to split the Bill between Users, when someone pays what they are due, you will decrease their balance by that amount and allocate it to the bill:
bill.record_payment_of(amount) user.decrease_balance_by(amount)
If there is an exception at the second step of the process, the numbers will be unbalanced, and the user will have a bigger balance left than they should’ve. To prevent this from happening, we can wrap this unit into a transaction, and if an exception is raised inside the block, the whole transaction is rolled back:
ActiveRecord::Base.transaction do bill.record_payment_of(amount) user.decrease_balance_by(amount) end
In the above example, we would want to force the rollback of the transaction if the user’s balance doesn’t cover the amount needed. To achieve this, we can raise an ActiveRecord::Rollback exception:
# app/models/user.rb def has_balance_for?(amount) ... end def decrease_balance_by(amount) raise ActiveRecord::Rollback unless has_balance_for?(amount) ... end The above code will roll back the transaction by raising the exception if there is no balance to cover the necessary amount. You might see that some people start the transaction by calling the transaction method on an actual model class instead of ActiveRecord::Base:Bill.transaction do bill.record_payment_of(amount) user.decrease_balance_by(amount) endSince your Active Record models are inheriting from ActiveRecord::Base, this is the same as calling transaction directly on the base class. Choosing one over the other is just a matter of preference.
It is worth noting that Active Record uses transactions internally for many operations to maintain data integrity. For instance, when you have a has_many relation, and you have dependent: :destroy set, Active Record will wrap this into a transaction, and if anything happens while destroying the dependent records, the transaction will be rolled back, and the data will stay in the original state.
That’s it for now, until next time!
30 May
Modify database schema with migrations - Rails Tricks Issue 9
Hi, this week, I will show you a few useful methods to modify your database schema with Active Record migrations.
You probably found yourself in a situation when a NOT NULL constraint needs to be removed or added to a table. Active Record provides a useful migration method to do so. To add a constraint, you can use:
change_column_null :table, :column, true
To remove it, you can use:
change_column_null :table, :column, false
If you also want to change the default value of the column in one go, you can pass the default value as the last parameter:
change_column_null :table, :column, false, 0
If you want to change the default value only(without changing the NULL constraint), use:
change_column_default :table, :column, "default value"
To have no default value, you need to change it to nil. If you want your migration to be reversible, you can specify a from and to value:
change_column_default :table, :column, from: nil, to: 0
It is rare in my experience, but you can also add or change comments on the database tables or columns. The methods for those are change_column_comments and change_table_comments. Just as when changing the default value, you can change the comment by specifying a new one:
change_column_comment :table, :column, "comment"
Or if you want the migration to be reversible, you can specify the from and to options:
change_column_comment :table, :column, from: "old comment", to: "new_comment"
I know this issue is pretty short, but good stuff comes in small quantities.