Austin Elixir – Routing Securely with Phoenix Framework – Installation



Austin Elixir – Routing Securely with Phoenix Framework – Installation

2 9


routing-securely-with-phoenix-framework

Routing Securely with Phoenix Framework for Austin Elixir on 2015-09-02 (http://www.meetup.com/Austin-Elixir/events/224755570/)

On Github KronicDeth / routing-securely-with-phoenix-framework

Austin Elixir

Routing Securely with Phoenix Framework

2015-09-02

Luke Imhoff

luke_imhoff@rapid7.com Kronic.Deth@gmail.com @limhoff-r7 @KronicDeth @KronicDeth

I am a Senior Software Engineer at Rapid7 in Austin. I works on Metasploit, a computer security project that provides information about security vulnerabilities and aids in penetration testing, written in Ruby, but I'm leading an effort to transition some of the project to Elixir.

In my free time I am the maintainer of IntelliJ Elixir, an Elixir plugin for JetBrains IDEs, like IntellIJ IDEA and Rubymine.

This Presentation

Live Stream/Recording https://youtu.be/lrlHYHKVWiI Slides Viewable https://kronicdeth.github.io/routing-securely-with-phoenix-framework Source https://github.com/KronicDeth/routing-securely-with-phoenix-framework/tree/gh-pages Project Source https://github.com/KronicDeth/routing-securely-with-phoenix-framework/tree/master

For viewers that want to follow along with their own copy of the slides or project source, they can be accessed at the shown addresses.

Outline

Installation New Project Configuration TLS Authentication CSRF Channels XSS Compromise Recovery

Installation

Node

Source Linux Mac Windows nodejs.org tar.gz pkg msi homebrew brew install node apt-get sudo apt-get install nodejs-legacy

Node is required for brunch.io, which is used for asset compilation. It is optional, but I'll be using it as it's easiest to setup when starting a new phoenix project.

I'm going to assume you already have Erlang and Elixir installed.

Installing Mix Archives

mix local.hex
mix archive.install https://github.com/phoenixframework/phoenix/releases/download/v0.17.0/phoenix_new-0.17.0.ez

mix local.hex ensures your version of the hex package manager is up-to-date.

The archive DOT install (archive.install) pulls down the phoenix DOT new (phoenix.new) task from github.

As you can see by the URL, this talk is based on Phoenix zero dot seventeen (0.17), which was the latest version as of 2015-08-20. zero DOT seventeen is the last release before one dot zero (1.0).

New Project

mix phoenix.new Options mix phoenix.new

Fetch and Install Dependencies

Run

mix phoenix.new Options

Option Description Format Example --app APP The name of the OTP application Atom routing_securely_with_phoenix_framework --database DATABASE Specify the database adapter for ecto
  • mssql
  • mysql
  • postgres (default)
  • sqlite
--module MODULE The name of the base module in the generated skeleton Alias RoutingSecurelyWithPhoenixFramework --no-ecto Do not generate Ecto files for the model layer --no-brunch Do not generate brunch files for static asset building

Normally, you don't need to specify any of these options and they are just derived from the directory name, but if you want to use a different naming scheme or nested namespaces, you'll need to manually override APP and MODULE.

By default, phoenix dot new (phoenix.new) will use Ecto with Postgres, but if you prefer a different database you can override that with dash dash database (--database). If you don't need a database at all, you can disable Ecto with dash dash no dash ecto (--no-ecto).

Even if you need a database, you could still create the project without ecto if you want to use a built-in Erlang database like Mnesia or DETS. Or a NOSQL alternative like Riak that Ecto doesn't support.

You could dash dash no dash brunch (--no-brunch) if you want to use something other than brunch, such as webpack, to compile your assets, or if you don't have HTML or assets because you're just building a JSON API or channel server.

mix phoenix.new

mix phoenix dot new (mix phoenix.new) with only a path will create config files for dev, prod, and test environments; a single endpoint for this project; views for errors and layout; ExUnit-based tests; a user socket for handling client connections for channels; a default page controller; templates for the layout and page views; the routes; the Ecto repo; and the default assets.

Fetch and Install Dependencies

mix phoenix dot new (mix phoenix.new) is interactive and after creating the files we saw on the last slide, the mix task will ask if you want to install dependencies.

Saying 'yes', will install the javascript dependencies with N-P-M install (npm install) and the Elixir dependencies with mix deps dot get (mix deps.get)

Please take note that the prompt only happens when creating a new project. If you clone a pre-existing project...

Fetching and Install Dependencies for Clone

Error Solution mix deps.get npm install

...you need to run mix deps dot get (mix deps.get) and N-P-M install (npm install) manually.

If you forget to run either of these steps and start mix phoenix dot server (mix phoenix.server), the log will helpfully contain errors telling you the command to run.

In Phoenix, and Elixir in general, if an error message isn't helpful it is considered a bug and you should open an issue with the project on Github.

Run your application

The output from mix phoenix dot new (mix phoenix.new) will now tell you to C-D (cd) to project directory; create the database; and run the server, but before you do that you should secure your database.

Configuration

Default Configuration

Environment Module key File Source Controlled? All Endpoint secret_key_base config/config.exs Yes dev Repo password config/dev.exs Yes prod Endpoint secret_key_base config/prod.secret.exs No prod Repo password config/prod.secret.exs No test Repo password config/test.exs Yes

Phoenix projects have two secrets: the secret key base and the database password.

The secret key base is used by Plug for the session encryption and signing and by Phoenix for channel tokens. If the secret key base is disclosed, it's possible to either read session cookies or fake cookies by signing session cookies containing your own data.

The database password is of course used to log into database. If the database password is disclosed all your data is accessible.

By default, the prod secrets are kept is a separate prod dot secret dot E-X-S (prod.secret.exs) that is git ignored. Not keeping production secrets in source control is a best practice.

But, by default, Phoenix does keep your development and test secrets in source control. I see two problems with this: first, it means that your entire team uses the same secrets and second, if your repository is public I can see your secrets on github.

You may think these aren't issues because they're not meant for production, but if you end up replicating part of your production data to development to fix a bug and some one gets access to your network, you can end up divulging production data to the attacker.

Additionally, if you have reused and/or weak password, an attacker can use your dev or test passwords to more easily guess your production passwords.

Remove shared secret_key_base

The first step to securing the configuration is to remove the shared secret_key_base from config/config.exs, which will force each environment to supply their own secret_key_base.

Secret configuration for each environment

Remove secret configuration import from config/prod.exs Import secret configuration for each environment in config/config.exs Ignore all secret configurations in git

The secrets for all configurations should be kept out of source control, so we'll remove the import config prod dot secret dot E-X-S (import_config "prod.secret.exs") from config slash prod dot E-X-S (config/prod.exs) and add an import that uses mix dot env (Mix.env) to import an environment-specific secret configuration in config slash config dot E-X-S (config/config.exs).

Generating new secret_key_base

iex(1)> length = 64
64
iex(2)> :crypto.strong_rand_bytes(length) |> Base.encode64 |> binary_part(0, length)

crypto strong rand bytes (:crypto.strong_random_bytes) is the equivalent of using secure random (SecureRandom) in Ruby or reading from slash dev slash random (/dev/random) in Linux.

This is the same code as mix phoenix dot new (mix phoenix.new) uses.

Generating database password

  • Use a password manager
  • Make the password long (> 16 characters)
  • Replace password every 90 days

The advice for a strong password applies even more so for prod than it does for dev or test.

In addition to replacing your passwords every 90 days you should do the same for your secret key bases. This will invalidate any active sessions that the user has, but this is a good thing because your users should also be replacing their passwords every 90 days.

Here, I'm using KeePassX to generate a strong password and store it, so I have it on file if I accidentally delete my secret configuration files.

Dev Secrets

Remove Repo configuration from config/dev.exs Create config/dev.secret.exs
use Mix.Config

# In this file, we keep development configuration that
# you likely want to automate and keep it away from
# your version control system.
config :routing_securely_with_phoenix_framework, RoutingSecurelyWithPhoenixFramework.Endpoint,
  secret_key_base: "SECRET_KEY_BASE"

# Configure your database
config :routing_securely_with_phoenix_framework, RoutingSecurelyWithPhoenixFramework.Repo,
  adapter: Ecto.Adapters.Postgres,
  database: "routing_securely_with_phoenix_framework_dev",
  host: "127.0.0.1",
  password: "PASSWORD",
  size: 20, # The amount of database connections in the pool
  username: "routing_securely_with_phoenix_framework_dev"

The config slash dev dot secret dot exs (config/dev.secret.exs) will have the secret key base for the endpoint and the entire repo configuration.

You could put the common keys for the repo configuration in config slash config dot E-X-S (config/config/exs), but this way the reader doesn't need to know which keys are defaults.

Notice that I'm using a separate username, that matches the database name. This way the user can have the minimum permissions to only interact with its own database.

Test Secrets

Remove Repo configuration from config/test.exs Create config/test.secret.exs
use Mix.Config

# In this file, we keep development configuration that
# you likely want to automate and keep it away from
# your version control system.
config :routing_securely_with_phoenix_framework, RoutingSecurelyWithPhoenixFramework.Endpoint,
  secret_key_base: "SECRET_KEY_BASE"

# Configure your database
config :routing_securely_with_phoenix_framework, RoutingSecurelyWithPhoenixFramework.Repo,
  adapter: Ecto.Adapters.Postgres,
  database: "routing_securely_with_phoenix_framework_test",
  password: "PASSWORD",
  pool: Ecto.Adapters.SQL.Sandbox,
  username: "routing_securely_with_phoenix_framework_test"

The secrets for test look the same as those for dev, except for the removal of the size option and addition of the poolin Repo.

Create PostgreSQL Users

Termianl pgAdmin 1 createuser --createdb --encrypted --no-createrole --no-superuser --pwprompt USERNAME Start pgAdmin3 2 Enter password Connect to server 3 Enter password (again) Right-click Login Roles 4 Click New Login Role 5 Enter username as Role Name 6 Change to Definition Tab 7 Enter password 8 Enter password (again) 9 Change to Role Privileges Tab 10 Click "Can create database" 11 Click OK

I normally use pgAdmin3 to create users and database as it's easier to check my work, but if you're following along and prefer something you can copy and paste into the terminal, I've included that.

Create the database

mix ecto.create for new projectmix ecto.create in built project

Now with the secrets outside of source control, we can create the database using the command that mix phoenix dot new (mix phoenix.new) gave us.

You may wonder what's going on when you run the command: you'll see all this output about files being compiled and wonder why you're compiling to create a database. Mix compiles the dependencies before creating the database because Ecto is a dependency of your project and so needs to be built for it to connect to PostgreSQL and create the database.

If you run mix ecto dot create (mix ecto.create) in a project where the dependencies are already compiled, then you'll just get the bottom message. Seeing the compilation message for ecto dot create (ecto.create) is a side-effect of it being the first mix command you ran.

TLS

Overview

  • Transport Layer Security
  • Successor to SSL (Secure Socket Layer) 3.0
  • TLS 1.0 was defined in 1999

H-T-T-P-S should no longer use S-S-L. When speaking about the encryption for H-T-T-P-S, you should say T-L-S now.

Phoenix, through Plug has built-in support for T-L-S without the need for a reverse proxy like engine-X (nginx).

We want to use TLS because we will be authenticating users, so if we don't use TLS, the user's passwords and cookies will be sent plain-text on the network, allowing attackers to steal them and impersonate the user.

Certificate Authority Key

openssl genrsa -des3 -out certificate-authority.key 4096 Option Argument Description genrsa Generate RSA key -des3 Password protect the private key -out certificate-authority.key Private Key file 4096 Number of bits in RSA key

To start making our own certificate authority, we need to generate a private key for the certificate authority.

Use your password manager to generate a long password for this private key.

We'll choose 4096 bits to be extra careful. The current recommendation is only 2048 bits.

Self-Signing Certificate Authority

openssl req -new -x509 -days 365 -key certificate-authority.key -out certificate-authority.crt -sha256 Option Argument Description req X.509 Certificate Signing Request (CSR) Management -new New Certificate Request -x509 Self-signed certificate -days 365 Number of days certificate is valid -key certificate-authority.key Input file name for private key used for signing -out certificate-authority.crt Output file for self-signed certificate -sha256 Sign using SHA256 instead of SHA1

Non-self-signed certificates usually last from 1 to 3 years.

Self-Signing Certificate Authority Distinguished Name

Viewing your self-signed certificate

openssl x509 -in certificate-authority.crt -noout -text

It's a good idea to check your work, which you can do with the X-509 subcommand for openssl.

You can see that the Issuer and the Subject have the same distinguished name, which is the hallmark of a self-signed certificate.

Server Key

openssl genrsa -out server-${MIX_ENV}.key 4096

We'll generate a separate server key for each mix environment so that the server certificate only works for the domain for each mix environemnt and we'll be alerted if we start using the development or test URL with the wrong server.

Server Certificate Signing Request

openssl req -new -key server-${MIX_ENV}.key -out server-${MIX_ENV}.csr

Since the serer certificate is not self-signed, signing it is broken into two parts: (1) the request for the server's key to be signed and (2) the certificate authority actually doing the signing.

This two-part process makes sense when you think of how real Certificate Authorities like Verisign have to sign your private key, but you don't want to send it to them to do that.

Server Certificate Signing Request Distinguished Name

I'll use the same country, state, locality, organization and email address as with the self-signed certificate authority, but the Common Name for the server must match the address I'll use to connect to the server, so in this case I'm using dev dot phoenix dot localhost under the assumption that this is for MIX_ENV=dev and I'll setup an etc host entry for dev dot phoenix dot locahost.

Signing Server Certificate Request

openssl x509 ‑CA certificate‑authority.crt ‑CAcreateserial ‑CAkey certificate‑authority.key ‑days 90 ‑in server‑${MIX_ENV}.csr ‑out server‑${MIX_ENV}.crt ‑req ‑sha256 Option Argument Description x509 Certificate display and signing utility ‑CA certificate‑authority.crt The Certificate Authority certificate ‑CAcreateserial Create file for keeping track of CA issued certificate serial numbers if it does not exist and assign this certificate the next serial number. ‑CAkey certificate‑authority.key Certificate Authority private Key for signing request ‑days 90 Days the server certificate is valid ‑in server‑${MIX_ENV}.csr Request to be signed ‑out server‑${MIX_ENV}.crt Signed certificate output file ‑req Sign a certificate request ‑sha256 Sign with SHA256 instead of SHA1

Trust Self-Signed Certificate Authority

OSX

Open Keychain Access Click Plus Select certificate-authority.crt Highlight the certificate Click i Expand Trust Change "When using this certificate" to Always Trust Close i Dialog Enter password to save changes

The whole purpose of creating a self-signed certificate authority instead of a single self-signed server certificate, was so that only one certificate needed to be trusted for all environments.

Trusting the certificate authority will turn the padlock from red to green and stop certificate warnings from your browser.

Fully Qualified Domain Setup

Open /etc/hosts

Add the following lines

127.0.0.1 dev.routing-securely-with-phoenix-framework.localhost
127.0.0.1 prod.routing-securely-with-phoenix-framework.localhost
127.0.0.1 test.routing-securely-with-phoenix-framework.localhost
Restart Browsers

In order to use your server certificates, you'll need a way for their common names to point at your local server.

To keep it simple, I'm just adding entries to slash etc slash hosts (/etc/hosts) instead of setting up local DNS.

TLS Configuration

We need to disable the default HTTP Strict Transport Security because it doesn't work with HTTPS ports other than 443 that are different than the HTTP port.

The https setting needs to know where to find the private key and certificate for the server. Using the OTP app setting allows the paths to be relative.

The host in the url setting needs to match the Common Name used in the respective mix environments, so I just use Mix.env in the host name itself.

TLS dev Configuration

For the redirect from HTTP to HTTPS to work, the force_ssl setting needs to know the HTTPS port, which means that setting is repeated here and for the https port setting itself, so I use variables to show the calculation based on the standard ports of 80 for http and 443 for https and the standard http port of 4000 for phoenix, which means a https port of 4363 to maintain the gap between 80 and 443.

TLS prod Configuration

Prod follows the same pattern as dev, but without the need for the gap calculation since prod uses the standard ports for http and https.

TLS test Configuration

Test follows the same pattern as dev.

Testing TLS connection

Start Phoenix:
mix phoenix.server
Open HTTP URL printed by Cowboy Verify redirected to HTTPS URL printed by Cowboy Verify connection is private

With the certificate authority trusted and phoenix server over TLS we have the green padlock and because we used to high bit count, there are no warnings from chrome that the certificate is weak.

Authentication

Authentication Providers

Don't store passwords if you don't have

If you are making an application on the internet, please consider NOT having your users create a new account and password for your site. Then you site just becomes another site that can have a password breach.

Instead, let password security be someone else's problem: use OAuth to allow sign-in from social networks or use SAML with Okta and allow users to login with their private company ActiveDirectory credentials.

If none of these options work for you or you want a fallback in case your users don't have accounts at the other providers, then you can implement passwords in your application.

This code for this section is based on Michael Eatherly's "Phoenix app with authentication" from May 11th 2015. It has been updated for changes in newer versions of Phoenix and some stylistic changes.

User model

mix phoenix.gen.model User users name password_hash
Argument Description User Module name users Table name name Column name password_hash Column name

Phoenix has generators for creating models.

User Migration

defmodule RoutingSecurelyWithPhoenixFramework.Repo.Migrations.CreateUser do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :name, :string, null: false
      add :password_hash, :string, null: false

      timestamps
    end

    create unique_index(:users, [:name])
  end
end

We create a bare minimum user with a unique name and hashed-password.

Note that I called the field password_hash because it's a hashed password it's not encrypted. A lot of example code confuses encrypted passwords, which can be unencrypted and recovered with hashed passwords, which can't be recovered. Hash is one-way. Hash password are only cracked by finding the input string that happens to make the same hash as the hash on file.

If you ever have to choice between encrypting or hashing passwords, choose hashing