Archive for June, 2014


Learning to Deploy Servers with Ansible

provisions

I’ve been wanting to learn how to use a configuration management tool for a long time. In the past I’ve attempted to learn Chef though I found myself getting lost amidst the domain language. Trying to use these tools made me feel extremely stupid simply because I was having a hard enough time grasping the concepts of the system.

A couple of weeks ago during our Dev Show and Tells, Nate Smith showed off Ansible. Ansible a configuration management tool but it’s goals are to be easier to use and understand. It’s written in python and uses YAML files for defining your provisioning tasks, called Playbooks in Ansible.

Before we can start using a Playbook we need to do a tiny bit of setup first. You’ll need to define your Server Inventory which is required before Ansible can go out and provision your servers for you.

# hosts
[webservers]
web1.example.com

[databases]
db2.example.com

Now let’s verify that our host configuration is correct:

/Users/csaunders/development/ansible [csaunders@outerhaven] [23:09]
> ansible all -i hosts -m ping
web1.example.com | success >> {
    "changed": false,
    "ping": "pong"
}

db1.example.com | success >> {
    "changed": false,
    "ping": "pong"
}

Now that we know that we are able to connect to our servers, let’s build a Playbook. A Playbook is simply a definition as to what kinds of tasks you’ll be running to setup the server. The simplest playbook you could have would be one where you install apache2 on a bunch of servers:

---
# Here we define the hosts we are going to run them on
- hosts: all
  user: root
  tasks:
    - name: Install Apache with php
      apt: pkg=apache2 state=present
    - name: Install mod PHP on Apache
      apt: pkg=libapache2-mod-php5 state=present
    - name: Install PHP
      apt: pkg=php5 state=present
    - name: Install the PHP MySQL Bindings
      apt: pkg=php5-mysql state=present
    - name: Restart Apache2
      service: name=apache2 state=restarted

What I really like about this is it’s very easy to understand what is going on. We have a little description that describes what the task is and the command that will be run. The name of the task will also be echoed into the terminal when you run the playbook so you have an idea of what task to look at if something goes wrong. And while you might be thinking that the above is pretty verbose, Ansible also provides the tools such that you can run the same command on a list of things. So you can shorten the above example into a single command.

- hosts: all
  user: root
  tasks:
    - name: Install Apache2 and the necessary PHP modules
      apt: pkg={{ item }} state=present
      with_items:
        - apache2
        - libapache2-mod-php5
        - php5
        - php5-mysql
    - name: Restart Apache2
      service: name=apache2 state=restarted

Often we want to be able to re-use a bunch of the code we write because many different kinds of servers use many of the same things. This is where we can define Roles which we can assign to various hosts or groups of hosts. A Role consists of a number of things though the major components are the tasks to run, templates to use and handlers that fire after a task.

Now with a few roles defined you can create Playbooks that leverage them:

---
- hosts: webservers
  user: ansible
  sudo: yes

  roles:
    - common
    - mysql
    - apache2

All the tasks contained in each role will be executed, and if all goes well you will have a server that’s ready to serve up some MySQL powered sites!

So far I have to say that I’m quite happy with Ansible. I’ve been meaning to migrate my systems around because they were somewhat out of date and I wanted to bring some costs down. The idea of doing it all manually was a massive deterrent from doing it whatsoever simply because of all the work that’s involved. While it might have taken me more time to get what I needed to do in Ansible; I know that if I ever need to do something like upgrade WordPress, it just requires changing a configuration value and running a command. It also makes it a lot easier for me to do things like change providers if I ever wanted. Learning to use this tool has also laid a base from which I can build upon. The process for creating a configuration to prepare a server for a Go or Ruby app should be far simpler! If you are looking to level up your dev ops knowledge, then I’d definitely consider looking into Ansible as a way to do that.


The Problem with shopify_app’s Session usage

An issue was recently opened on the Shopify App project because of a change in what Rails allowed you to set inside a session. This caused the project to break for people looking to play with the API simply because they couldn’t set their session up correctly.

I’ve rarely used the gem simply because its generators are very noisy, but after talking to a friend about this I became extremely aware of what the problem was. The following is the offending code:

  sess = ShopifyAPI::Session.new(
                            params[:shop],
                            response['credentials']['token'] 
                          )
  session[:shopify] = sess        
  flash[:notice] = "Logged in"
  redirect_to return_address

Now why is this a problem? By default, Rails uses cookies for storing sessions and cookies are sent off to your client. Thankfully the cookies are run through an HMAC so they cannot be tampered with, but the data is still raw and available for anyone to read.

Reading the Session

This can easily be shown by grabbing the contents of our cookie from chrome:

broken cookies yo

There are a few things that need to be massaged before we can read the data, that primarily being the trailing –ae63169593d0e2e68a72977324f5791d6d31b8b8 which I believe is the HMAC. Also the %3D needs to be converted back into a = for us to work with the data correctly. You can use a utility like Rack::Utils#unescape to take care of this if you don’t want to do it by hand. The contents of the cookie are simply Base64 encoded, so let’s decode the message and see what’s there:

require 'base64'

msg = "BAh7C0kiD3Nlc3Npb25faWQGOgZFVEkiJTZmZWVlNzlhOTA4YzBlOGQ3YzYwM2U3ZmY4OWRjMGFkBjsAVEkiDnJldHVybl90bwY7AEZJIgYvBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMW1MVlJITnFoNjJ4UllScDA2VEpPNkVoQWpLSkZoaVF2eFlnczIyMDhrV0E9BjsARkkiE29tbmlhdXRoLnN0YXRlBjsAVEkiNWU4NTJmZTM1YzZjZWYyYmY2NWJjYjIzY2RjMDllOWRkODkyZjUwMzE4YTNlMWNiNgY7AEZJIgxzaG9waWZ5BjsARm86GFNob3BpZnlBUEk6OlNlc3Npb24HOglAdXJsSSIbanVzdG1vcHMubXlzaG9waWZ5LmNvbQY7AFQ6C0B0b2tlbkkiJWI4YmMxNjQyMmUyODFhODM3MDhhN2NlNmIxYzRhZWZiBjsAVEkiCmZsYXNoBjsAVG86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToKQHVzZWRvOghTZXQGOgpAaGFzaHsGOgtub3RpY2VUOgxAY2xvc2VkRjoNQGZsYXNoZXN7BjsNSSIOTG9nZ2VkIGluBjsAVDoJQG5vdzA="

raw_contents = Base64.strict_decode64(msg)

raw contents

Woah… so that’s looking pretty hairy and by just looking over it visually we can see some sensitive data in there. But let’s make things even easier on ourselves. We can use the Marshal module to actually convert that data into objects that are super easy to read and use.

# require the libraries so we can unmarshal the data
require 'action_dispatch'
require 'shopify_api'

session = Marshal.load(raw_contents)
#> {
    "session_id"=>"6feee79a908c0e8d7c603e7ff89dc0ad",
    "return_to"=>"/",
    "_csrf_token"=>"mLVRHNqh62xRYRp06TJO6EhAjKJFhiQvxYgs2208kWA=",
    "omniauth.state"=>"e852fe35c6cef2bf65bcb23cdc09e9dd892f50318a3e1cb6",
    "shopify"=>
      #,
    "flash"=>
      #,
        @closed=false,
        @flashes={:notice=>"Logged in"},
        @now=nil>
   }
shopify = session['shopify']
puts "Domain: #{shopify.url}, Access Token: #{shopify.token}"
> Domain: justmops.myshopify.com, Access Token: b8bc16422e281a83708a7ce6b1c4aefb

Now it’s plain as day! We were storing raw Shopify session objects in the cookies that we are sending back to our clients and we were able to decode them. Depending on your configuration this could mean that malicious third parties could hijack your access token and do some really bad things to a merchants shop.

How do I avoid this?

The rails security guidelines suggest against storing any kind of sensitive data in the session and this style of vulnerability is the #2 in the OWASP Top 10 vulnerabilities of 2013. The default way that it was done in shopify_app was definitely something you’d want avoid in a production environment. Unfortunately nothing was put in place to help provide the education needed to ensure people moved away from this practice.

When building an application you want to ensure that sensitive information never goes anywhere you cannot trust. While it is convenient to store the data in the session, the client is about as untrustworthy as you can get. What should be stored in the session is something that you can use to identify the data you need.

How to Fix it

In a patch that has been merged into master, you instead configure a ShopifySessionRepository such that it knows how to store and retrieve sessions from your server. For example, you could simply have your Shop model respond to those two methods and you’d be set. When a session is stored you return the ID for the model such that you can retrieve a ShopifyAPI::Sesssion when you need it at a later date.

A new version of the shopify_app gem was just released, so you can easily upgrade your application by simply performing gem update shopify_app which will upgrade you to version 5.0.0. If you are starting a new app you’ll want to ensure that your Gemfile is using the newest version such that you are able to use the safer session implementation.

gem 'shopify_app', ">= 5.0.0"

If you are using the shopify_app in your project, take a look at your sessions controller to verify that your show action doesn’t have the offending code as mentioned above. If it does, you should remove that code and replace it with a safer way to access the credentials associated with a shop. Consider looking into what was added to shopify_app for one approach to this problem.


Building Games for Fun, Feedback and F…. Learning

Last week I did a presentation at the Ottawa Ruby group. Luckily we were running a live stream of it and I was able to grab a copy of the raw video. You can check it out below: