On Github KronicDeth / routing-securely-with-phoenix-framework
2015-09-02
Luke Imhoff
luke_imhoff@rapid7.com Kronic.Deth@gmail.com @limhoff-r7 @KronicDeth @KronicDethI 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.
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.
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.
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).
Fetch and Install Dependencies
RunNormally, 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 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.
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...
...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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
Non-self-signed certificates usually last from 1 to 3 years.
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.
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.
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.
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.
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.
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.localhostRestart 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.
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.
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.
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.
mix phoenix.serverOpen 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.
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.
mix phoenix.gen.model User users name password_hashArgument Description User Module name users Table name name Column name password_hash Column name
Phoenix has generators for creating models.
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