-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 554a598
Showing
31 changed files
with
2,274 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# App artifacts | ||
/_build | ||
/db | ||
/deps | ||
/*.ez | ||
|
||
# Generated on crash by the VM | ||
erl_crash.dump | ||
|
||
# The config/prod.secret.exs file by default contains sensitive | ||
# data and you should not commit it into version control. | ||
# | ||
# Alternatively, you may comment the line below and commit the | ||
# secrets file as long as you replace its contents by environment | ||
# variables. | ||
/config/prod.secret.exs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# Validating GitHub Webhooks with phoenix | ||
## TL;DR | ||
|
||
* GitHub hashes the raw body of their messages | ||
* You need to implement a custom parser to calculate the hash and still parse the JSON. | ||
* Attempting to to implement a plug that reads the body before the parser renders the parsers unworkable. The body is read-once. | ||
* This repo is an example of how to do it in Phoenix. | ||
* Environment variables need to be available at compile time. | ||
|
||
## What are we going to do? | ||
|
||
GitHub web hooks are a pretty cool way to monitor what is going on with | ||
your code on GitHub. Want to take some automated action when someone pushes | ||
to the master branch? Webhooks are very useful for that. However, | ||
you don't want your application to act on just any POST request that comes in. | ||
You need to be sure that the data your app is receiving really came from GitHub. | ||
|
||
GitHub provides this assurance by signing the messages that it sends to you. | ||
When you go into settings and set up a web hook you have the option of specifying | ||
a key. GitHub will use that key to create a cryptographic hash of every message it | ||
sends to you. When you receive a message you can calculate the hash yourself and | ||
compare it to the hash GitHub provided thus proving that the message was sent by | ||
some service that knows the key. | ||
|
||
There are some challenges we need to get past, though. The biggest one is that you | ||
have to read the body of the incoming request in its original form, in other words before | ||
any parsers have had a chance to alter it. However, reading the body makes the body | ||
inaccessible to anything else that needs to read the message body. So we can't just | ||
make a simple function, we need to replace the JSON parser with a new parser that will | ||
also create a copy of the unaltered body and put that in the `conn`. Let's get started. | ||
|
||
## Create project and add dependencies | ||
|
||
`mix phoenix.new github_webhooks --no-html --no-brunch` | ||
|
||
Add this to `mix.exs`. When we compare the hash value provided by GitHub to the | ||
hash value that we calculate we want to make sure that we're not opening ourselves up | ||
to [timing attacks](https://codahale.com/a-lesson-in-timing-attacks/). So we need to do | ||
a secure comparison instead of a simple `==`. | ||
|
||
`{:secure_compare, "~> 0.0.1"},` | ||
|
||
and run `mix deps.get` | ||
|
||
## Create an endpoint for the webhooks. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# This file is responsible for configuring your application | ||
# and its dependencies with the aid of the Mix.Config module. | ||
# | ||
# This configuration file is loaded before any dependency and | ||
# is restricted to this project. | ||
use Mix.Config | ||
|
||
# General application configuration | ||
config :github_webhooks, | ||
ecto_repos: [GithubWebhooks.Repo] | ||
|
||
# Configures the endpoint | ||
config :github_webhooks, GithubWebhooks.Endpoint, | ||
url: [host: "localhost"], | ||
secret_key_base: "YkbRzfc8LFlyv2k0jAFNiC7La7G+IjmGvpRoZbhmw6ZqLIt/Bf1BAt0ZSpa+P/+K", | ||
render_errors: [view: GithubWebhooks.ErrorView, accepts: ~w(json)], | ||
pubsub: [name: GithubWebhooks.PubSub, | ||
adapter: Phoenix.PubSub.PG2] | ||
|
||
# Configures Elixir's Logger | ||
config :logger, :console, | ||
format: "$time $metadata[$level] $message\n", | ||
metadata: [:request_id] | ||
|
||
# Import environment specific config. This must remain at the bottom | ||
# of this file so it overrides the configuration defined above. | ||
import_config "#{Mix.env}.exs" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
use Mix.Config | ||
|
||
# For development, we disable any cache and enable | ||
# debugging and code reloading. | ||
# | ||
# The watchers configuration can be used to run external | ||
# watchers to your application. For example, we use it | ||
# with brunch.io to recompile .js and .css sources. | ||
config :github_webhooks, GithubWebhooks.Endpoint, | ||
http: [port: 4000], | ||
debug_errors: true, | ||
code_reloader: true, | ||
check_origin: false, | ||
watchers: [] | ||
|
||
|
||
# Do not include metadata nor timestamps in development logs | ||
config :logger, :console, format: "[$level] $message\n" | ||
|
||
# Set a higher stacktrace during development. Avoid configuring such | ||
# in production as building large stacktraces may be expensive. | ||
config :phoenix, :stacktrace_depth, 20 | ||
|
||
# Configure your database | ||
config :github_webhooks, GithubWebhooks.Repo, | ||
adapter: Ecto.Adapters.Postgres, | ||
username: "postgres", | ||
password: "postgres", | ||
database: "github_webhooks_dev", | ||
hostname: "localhost", | ||
pool_size: 10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
use Mix.Config | ||
|
||
# For production, we configure the host to read the PORT | ||
# from the system environment. Therefore, you will need | ||
# to set PORT=80 before running your server. | ||
# | ||
# You should also configure the url host to something | ||
# meaningful, we use this information when generating URLs. | ||
# | ||
# Finally, we also include the path to a manifest | ||
# containing the digested version of static files. This | ||
# manifest is generated by the mix phoenix.digest task | ||
# which you typically run after static files are built. | ||
config :github_webhooks, GithubWebhooks.Endpoint, | ||
http: [port: {:system, "PORT"}], | ||
url: [host: "example.com", port: 80], | ||
cache_static_manifest: "priv/static/manifest.json" | ||
|
||
# Do not print debug messages in production | ||
config :logger, level: :info | ||
|
||
# ## SSL Support | ||
# | ||
# To get SSL working, you will need to add the `https` key | ||
# to the previous section and set your `:url` port to 443: | ||
# | ||
# config :github_webhooks, GithubWebhooks.Endpoint, | ||
# ... | ||
# url: [host: "example.com", port: 443], | ||
# https: [port: 443, | ||
# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), | ||
# certfile: System.get_env("SOME_APP_SSL_CERT_PATH")] | ||
# | ||
# Where those two env variables return an absolute path to | ||
# the key and cert in disk or a relative path inside priv, | ||
# for example "priv/ssl/server.key". | ||
# | ||
# We also recommend setting `force_ssl`, ensuring no data is | ||
# ever sent via http, always redirecting to https: | ||
# | ||
# config :github_webhooks, GithubWebhooks.Endpoint, | ||
# force_ssl: [hsts: true] | ||
# | ||
# Check `Plug.SSL` for all available options in `force_ssl`. | ||
|
||
# ## Using releases | ||
# | ||
# If you are doing OTP releases, you need to instruct Phoenix | ||
# to start the server for all endpoints: | ||
# | ||
# config :phoenix, :serve_endpoints, true | ||
# | ||
# Alternatively, you can configure exactly which server to | ||
# start per endpoint: | ||
# | ||
# config :github_webhooks, GithubWebhooks.Endpoint, server: true | ||
# | ||
# You will also need to set the application root to `.` in order | ||
# for the new static assets to be served after a hot upgrade: | ||
# | ||
# config :github_webhooks, GithubWebhooks.Endpoint, root: "." | ||
|
||
# Finally import the config/prod.secret.exs | ||
# which should be versioned separately. | ||
import_config "prod.secret.exs" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
use Mix.Config | ||
|
||
# We don't run a server during test. If one is required, | ||
# you can enable the server option below. | ||
config :github_webhooks, GithubWebhooks.Endpoint, | ||
http: [port: 4001], | ||
server: false | ||
|
||
# Print only warnings and errors during test | ||
config :logger, level: :warn | ||
|
||
# Configure your database | ||
config :github_webhooks, GithubWebhooks.Repo, | ||
adapter: Ecto.Adapters.Postgres, | ||
username: "postgres", | ||
password: "postgres", | ||
database: "github_webhooks_test", | ||
hostname: "localhost", | ||
pool: Ecto.Adapters.SQL.Sandbox |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
defmodule GithubWebhooks do | ||
use Application | ||
|
||
# See http://elixir-lang.org/docs/stable/elixir/Application.html | ||
# for more information on OTP Applications | ||
def start(_type, _args) do | ||
import Supervisor.Spec | ||
|
||
# Define workers and child supervisors to be supervised | ||
children = [ | ||
# Start the Ecto repository | ||
supervisor(GithubWebhooks.Repo, []), | ||
# Start the endpoint when the application starts | ||
supervisor(GithubWebhooks.Endpoint, []), | ||
# Start your own worker by calling: GithubWebhooks.Worker.start_link(arg1, arg2, arg3) | ||
# worker(GithubWebhooks.Worker, [arg1, arg2, arg3]), | ||
] | ||
|
||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html | ||
# for other strategies and supported options | ||
opts = [strategy: :one_for_one, name: GithubWebhooks.Supervisor] | ||
Supervisor.start_link(children, opts) | ||
end | ||
|
||
# Tell Phoenix to update the endpoint configuration | ||
# whenever the application is updated. | ||
def config_change(changed, _new, removed) do | ||
GithubWebhooks.Endpoint.config_change(changed, removed) | ||
:ok | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
defmodule GithubWebhooks.Endpoint do | ||
use Phoenix.Endpoint, otp_app: :github_webhooks | ||
|
||
socket "/socket", GithubWebhooks.UserSocket | ||
|
||
# Serve at "/" the static files from "priv/static" directory. | ||
# | ||
# You should set gzip to true if you are running phoenix.digest | ||
# when deploying your static files in production. | ||
plug Plug.Static, | ||
at: "/", from: :github_webhooks, gzip: false, | ||
only: ~w(css fonts images js favicon.ico robots.txt) | ||
|
||
# Code reloading can be explicitly enabled under the | ||
# :code_reloader configuration of your endpoint. | ||
if code_reloading? do | ||
plug Phoenix.CodeReloader | ||
end | ||
|
||
plug Plug.RequestId | ||
plug Plug.Logger | ||
|
||
plug Plug.Parsers, | ||
parsers: [:urlencoded, :multipart, :json], | ||
pass: ["*/*"], | ||
json_decoder: Poison | ||
|
||
plug Plug.MethodOverride | ||
plug Plug.Head | ||
|
||
# The session will be stored in the cookie and signed, | ||
# this means its contents can be read but not tampered with. | ||
# Set :encryption_salt if you would also like to encrypt it. | ||
plug Plug.Session, | ||
store: :cookie, | ||
key: "_github_webhooks_key", | ||
signing_salt: "/86cL0VL" | ||
|
||
plug GithubWebhooks.Router | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
defmodule GithubWebhooks.Repo do | ||
use Ecto.Repo, otp_app: :github_webhooks | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
defmodule GithubWebhooks.Mixfile do | ||
use Mix.Project | ||
|
||
def project do | ||
[app: :github_webhooks, | ||
version: "0.0.1", | ||
elixir: "~> 1.2", | ||
elixirc_paths: elixirc_paths(Mix.env), | ||
compilers: [:phoenix, :gettext] ++ Mix.compilers, | ||
build_embedded: Mix.env == :prod, | ||
start_permanent: Mix.env == :prod, | ||
aliases: aliases(), | ||
deps: deps()] | ||
end | ||
|
||
# Configuration for the OTP application. | ||
# | ||
# Type `mix help compile.app` for more information. | ||
def application do | ||
[mod: {GithubWebhooks, []}, | ||
applications: [:phoenix, :phoenix_pubsub, :cowboy, :logger, :gettext, | ||
:phoenix_ecto, :postgrex]] | ||
end | ||
|
||
# Specifies which paths to compile per environment. | ||
defp elixirc_paths(:test), do: ["lib", "web", "test/support"] | ||
defp elixirc_paths(_), do: ["lib", "web"] | ||
|
||
# Specifies your project dependencies. | ||
# | ||
# Type `mix help deps` for examples and options. | ||
defp deps do | ||
[{:phoenix, "~> 1.2.0"}, | ||
{:phoenix_pubsub, "~> 1.0"}, | ||
{:phoenix_ecto, "~> 3.0"}, | ||
{:postgrex, ">= 0.0.0"}, | ||
{:gettext, "~> 0.11"}, | ||
{:cowboy, "~> 1.0"}] | ||
end | ||
|
||
# Aliases are shortcuts or tasks specific to the current project. | ||
# For example, to create, migrate and run the seeds file at once: | ||
# | ||
# $ mix ecto.setup | ||
# | ||
# See the documentation for `Mix` for more info on aliases. | ||
defp aliases do | ||
["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], | ||
"ecto.reset": ["ecto.drop", "ecto.setup"], | ||
"test": ["ecto.create --quiet", "ecto.migrate", "test"]] | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
%{"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], []}, | ||
"cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:rebar, :make], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]}, | ||
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []}, | ||
"db_connection": {:hex, :db_connection, "1.0.0-rc.4", "fad1f772c151cc6bde82412b8d72319968bc7221df8ef7d5e9d7fde7cb5c86b7", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: true]}, {:sbroker, "~> 1.0.0-beta.3", [hex: :sbroker, optional: true]}]}, | ||
"decimal": {:hex, :decimal, "1.1.2", "79a769d4657b2d537b51ef3c02d29ab7141d2b486b516c109642d453ee08e00c", [:mix], []}, | ||
"ecto": {:hex, :ecto, "2.0.4", "03fd3b9aa508b1383eb38c00ac389953ed22af53811aa2e504975a3e814a8d97", [:mix], [{:db_connection, "~> 1.0-rc.2", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}, {:mariaex, "~> 0.7.7", [hex: :mariaex, optional: true]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}, {:postgrex, "~> 0.11.2", [hex: :postgrex, optional: true]}, {:sbroker, "~> 1.0-beta", [hex: :sbroker, optional: true]}]}, | ||
"gettext": {:hex, :gettext, "0.11.0", "80c1dd42d270482418fa158ec5ba073d2980e3718bacad86f3d4ad71d5667679", [:mix], []}, | ||
"mime": {:hex, :mime, "1.0.1", "05c393850524767d13a53627df71beeebb016205eb43bfbd92d14d24ec7a1b51", [:mix], []}, | ||
"phoenix": {:hex, :phoenix, "1.2.1", "6dc592249ab73c67575769765b66ad164ad25d83defa3492dc6ae269bd2a68ab", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.1", [hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]}, | ||
"phoenix_ecto": {:hex, :phoenix_ecto, "3.0.1", "42eb486ef732cf209d0a353e791806721f33ff40beab0a86f02070a5649ed00a", [:mix], [{:ecto, "~> 2.0", [hex: :ecto, optional: false]}, {:phoenix_html, "~> 2.6", [hex: :phoenix_html, optional: true]}, {:plug, "~> 1.0", [hex: :plug, optional: false]}]}, | ||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.0", "c31af4be22afeeebfaf246592778c8c840e5a1ddc7ca87610c41ccfb160c2c57", [:mix], []}, | ||
"plug": {:hex, :plug, "1.2.0", "496bef96634a49d7803ab2671482f0c5ce9ce0b7b9bc25bc0ae8e09859dd2004", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]}, | ||
"poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []}, | ||
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []}, | ||
"postgrex": {:hex, :postgrex, "0.11.2", "139755c1359d3c5c6d6e8b1ea72556d39e2746f61c6ddfb442813c91f53487e8", [:mix], [{:connection, "~> 1.0", [hex: :connection, optional: false]}, {:db_connection, "~> 1.0-rc", [hex: :db_connection, optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}]}, | ||
"ranch": {:hex, :ranch, "1.2.1", "a6fb992c10f2187b46ffd17ce398ddf8a54f691b81768f9ef5f461ea7e28c762", [:make], []}} |
Oops, something went wrong.