diff --git a/.gitignore b/.gitignore
index a48eb5fe..16d9c4cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,8 @@
+# discourage people from committing env files, which are used for the
+# quickstart process and may contain secrets
+env
+
+
# Compiled source #
###################
*.com
diff --git a/README.md b/README.md
index 8b809670..5cfb1454 100644
--- a/README.md
+++ b/README.md
@@ -2,16 +2,23 @@
[![CircleCI](https://circleci.com/gh/buzzfeed/sso.svg?style=svg)](https://circleci.com/gh/buzzfeed/sso)
[![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT)
+[![Docker Automated build](https://img.shields.io/docker/automated/buzzfeed/sso.svg)](https://hub.docker.com/r/buzzfeed/sso/)
----
-BuzzFeed's **sso** is our single sign-on experience for our internal web services, lovingly known as the *S.S. Octopus* (octoboi) by our team. In addition to the source code, we publish an official [Docker image][docker_hub].
+**sso** — lovingly known as *the S.S. Octopus* or *octoboi* — is the
+authentication and authorization system BuzzFeed developed to provide a secure,
+single sign-on experience for access to the many internal web apps used by our
+employees.
-It is used to provide single-sign-on authentication and authorization for internal web applications behind it by ensuring that only people in a specific email domain (and optionally users in specific Google Groups) can access them. It consists of two processes - `sso-auth` and `sso-proxy`.
+It depends on Google as its authoritative OAuth2 provider, and authenticates
+users against a specific email domain. Further authorization based on Google
+Group membership can be required on a per-upstream basis.
-The main idea of **sso** is a "double OAuth2" flow, where `sso-auth` is the OAuth2 provider for `sso-proxy`, and Google (or another third-party provider), is the OAuth2 provider for `sso-auth`.
+The main idea behind **sso** is a "double OAuth2" flow, where `sso-auth` is the
+OAuth2 provider for `sso-proxy` and Google is the OAuth2 provider for `sso-auth`.
In a nutshell:
@@ -26,6 +33,17 @@ In a nutshell:
without needing to go through the Google OAuth2 flow
- `sso-proxy` transparently re-validates & refreshes the user's session with `sso-auth`
+## Installation
+
+- [Prebuilt binary releases](https://github.com/buzzfeed/sso/releases)
+- [Docker][docker_hub]
+- `go get github.com/buzzfeed/sso/cmd/...`
+
+## Quickstart
+
+Follow our [Quickstart guide](docs/quickstart.md) to spin up a local deployment
+of **sso** to get a feel for how it works!
+
## Code of Conduct
Help us keep **sso** open and inclusive. Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md).
diff --git a/docs/.DS_Store b/docs/.DS_Store
deleted file mode 100644
index a7b9fa2a..00000000
Binary files a/docs/.DS_Store and /dev/null differ
diff --git a/docs/google_provider_setup.md b/docs/google_provider_setup.md
new file mode 100644
index 00000000..1a2ce518
--- /dev/null
+++ b/docs/google_provider_setup.md
@@ -0,0 +1,129 @@
+# Google Provider Setup & Configuration
+
+**sso** uses Google as its authoritative OAuth provider, and there are some
+manual steps that must be followed to create and configure the necessary
+credentials.
+
+
+## 1. Create a Google Cloud project
+
+Use a web browser to access the Google Cloud [Resource Manager](
+https://console.developers.google.com/cloud-resource-manager). Log into your account, if you have
+not already done so.
+
+![Manage Resources](img/setup-manage_resources.jpg)
+
+Select "Create Project". Provide the requested details (Project Name, etc), and click "Create". You
+will have a notification when your project has finished being created; this should only take a
+few moments.
+
+![Notification](img/setup-notification.jpg)
+
+## 2. Generate OAuth credentials
+
+Once your project has been created, click [here](
+https://console.cloud.google.com/apis/credentials/consent) to configure the "OAuth consent screen"
+for your project. Make sure that the name of the project you have created appears next to the words
+"Google Cloud Platform" in the toolbar at the top of the website; if it does not, switch to your
+project by clicking the name that appears there, and selecting the project you just created.
+
+Fill in the field "Product name shown to users". We recommend the name `SSO Authenticator` for this
+field. Hit "Save" after filling in this field.
+
+![OAuth Consent Screen](img/setup-consent_screen.jpg)
+
+After this, click [here](https://console.cloud.google.com/apis/credentials) to configure
+credentials for the service. Click the button labeled "Create credentials", and select the option
+"OAuth client ID".
+
+![Create Credentials Dropdown](img/setup-create_credentials.jpg)
+
+The next screen will ask you to choose an "Application Type"; select "Web application". You will be
+asked to fill in three fields.
+- **Name**: Any appropriate name is fine (e.g. `Dev`).
+- **Authorized JavaScript origins**: Leave this field blank.
+- **Authorized redirect URIs**: Add the URI of your `sso-auth` deployment, with the path suffix
+`/oauth2/callback`. For example, if `sso-auth` will be accessible at the domain
+`sso-auth.example.com`, then add the URI `https://sso-auth.example.com/oauth2/callback`.
+
+**⚡️ Note:** If you're following the [Quickstart guide](quickstart.md), use
+`http://sso-auth.localtest.me` as the Authorized redirect URI.
+
+![Credentials](img/setup-credentials.jpg)
+
+Once all of this has been entered, click the "Create" button. A dialog box will open with two
+fields, a "client ID" and a "client secret". Copy both of these values to a safe and secure
+location before clicking the "OK" button to proceed. The "client ID" and "client secret" strings
+will be used to configure your `sso` deployment, as described below.
+
+**⚡️ Note:** If you're following the [Quickstart guide](quickstart.md), **stop here!**
+You'll add these credentials to `quickstart/env` as instructed in [the guide](quickstart.md).
+
+
+## 3. Set up a service account for Google Groups-based authorization
+
+If desired, `sso` can be configured to use Google Groups membership for authorization, only granting
+access to an upstream to users that are members of particular groups.
+
+### Create a service account
+
+Begin by creating a "service account". This will be the identity assumed by `sso` when requesting
+information about a user from Google. Go to the [Service Accounts] page in IAM/Admin, and verify
+that the correct project name appears in the dashboard at the top of the page before proceeding.
+Click "Create Service Account" toward the top of the page to begin creating the account.
+
+![Creating a service account](img/setup-create_service_account.jpg)
+
+Fill out the fields requested as follows:
+- **Service account name**: Any appropriate name is fine. We recommend `sso-authenticator`.
+- **Service account ID**: Google will generate this as you type the "account name". We recommend
+leaving as-is.
+- **Project role**: No project roles are required for `sso`.
+- **Furnish a new private key**: Check this box, and select `JSON` as the "Key type".
+- **Enable G Suite Domain-wide Delegation**: Check this box.
+
+Click the "Save" button after entering this information. Google will download a `.json` file
+through your browser containing a private key. Make sure not to lose this.
+
+In the [Credentials](https://console.cloud.google.com/apis/credentials) page, you should now see
+the service account that you just created listed under "OAuth 2.0 client IDs". Copy the "Client ID",
+as it will be used in the next step.
+
+### Authorizing use of the Admin SDK API
+
+Click [here](https://console.cloud.google.com/apis/library/admin.googleapis.com) to go to Google's
+page for the "Admin SDK" API. Click "Enable" to enable the API for your project. Note that if the
+API has already been enabled on your project, then you will see the word "API enabled" along with
+a green checkmark.
+
+![Admin API](img/setup-admin_api.jpg)
+
+Go to the [Google Admin console](https://admin.google.com); note that you may be prompted to log
+in again. Select "Security" from the available controls (click "More Controls" if it is not visible
+when the page first loads).
+
+![Security Admin Control](img/setup-security_control.jpg)
+
+Click the "Advanced settings" option, and select "Manage API client access" from the resulting
+panel.
+
+![API Client Access](img/setup-api_client_access.jpg)
+
+In the "Client Name" field, enter the Client ID of the service account that you have created. You
+will need to provide the following "API Scopes" for the service account that we have created:
+- `https://www.googleapis.com/auth/admin.directory.group.readonly`
+- `https://www.googleapis.com/auth/admin.directory.user.readonly`
+
+Type these into the field titled "One or More API Scopes", as a comma-separated list. After these
+have been filled in, click the "Authorize" button.
+
+### Configuring `sso` to use the service account credentials
+
+To give `sso-auth` permission to access Google Group information for users, the following
+environment variables must be set:
+
+- **`BUZZFEED_GOOGLE_ADMIN_EMAIL`**: An administrative email address on your organization's
+domain, the identity of which can be assumed by `sso`.
+- **`BUZZFEED_GOOGLE_SERVICE_ACCOUNT_JSON`**: The path to the JSON file downloaded at the time of
+service account creation above. There is no reason why this file should ever be accessed by any
+person or service other than `sso`; ensure that file permissions are set accordingly.
diff --git a/docs/img/choose-account.jpg b/docs/img/choose-account.jpg
new file mode 100644
index 00000000..4c5ad074
Binary files /dev/null and b/docs/img/choose-account.jpg differ
diff --git a/docs/img/logo.png b/docs/img/logo.png
new file mode 100644
index 00000000..55a3a6f7
Binary files /dev/null and b/docs/img/logo.png differ
diff --git a/docs/img/payload-screen.jpg b/docs/img/payload-screen.jpg
new file mode 100644
index 00000000..4a74e8df
Binary files /dev/null and b/docs/img/payload-screen.jpg differ
diff --git a/docs/img/setup-admin_api.jpg b/docs/img/setup-admin_api.jpg
new file mode 100644
index 00000000..87d727bf
Binary files /dev/null and b/docs/img/setup-admin_api.jpg differ
diff --git a/docs/img/setup-api_client_access.jpg b/docs/img/setup-api_client_access.jpg
new file mode 100644
index 00000000..3d8d0133
Binary files /dev/null and b/docs/img/setup-api_client_access.jpg differ
diff --git a/docs/img/setup-consent_screen.jpg b/docs/img/setup-consent_screen.jpg
new file mode 100644
index 00000000..f7fd7c90
Binary files /dev/null and b/docs/img/setup-consent_screen.jpg differ
diff --git a/docs/img/setup-create_credentials.jpg b/docs/img/setup-create_credentials.jpg
new file mode 100644
index 00000000..f4714ba8
Binary files /dev/null and b/docs/img/setup-create_credentials.jpg differ
diff --git a/docs/img/setup-create_service_account.jpg b/docs/img/setup-create_service_account.jpg
new file mode 100644
index 00000000..d1bd6b63
Binary files /dev/null and b/docs/img/setup-create_service_account.jpg differ
diff --git a/docs/img/setup-credentials.jpg b/docs/img/setup-credentials.jpg
new file mode 100644
index 00000000..2c66725e
Binary files /dev/null and b/docs/img/setup-credentials.jpg differ
diff --git a/docs/img/setup-manage_resources.jpg b/docs/img/setup-manage_resources.jpg
new file mode 100644
index 00000000..91a5eda5
Binary files /dev/null and b/docs/img/setup-manage_resources.jpg differ
diff --git a/docs/img/setup-notification.jpg b/docs/img/setup-notification.jpg
new file mode 100644
index 00000000..5471e101
Binary files /dev/null and b/docs/img/setup-notification.jpg differ
diff --git a/docs/img/setup-security_control.jpg b/docs/img/setup-security_control.jpg
new file mode 100644
index 00000000..fd6215db
Binary files /dev/null and b/docs/img/setup-security_control.jpg differ
diff --git a/docs/img/start-auth.jpg b/docs/img/start-auth.jpg
new file mode 100644
index 00000000..0eba5e19
Binary files /dev/null and b/docs/img/start-auth.jpg differ
diff --git a/docs/img/start-script.jpg b/docs/img/start-script.jpg
new file mode 100644
index 00000000..36e8f320
Binary files /dev/null and b/docs/img/start-script.jpg differ
diff --git a/docs/quickstart.md b/docs/quickstart.md
new file mode 100644
index 00000000..fb1b2c99
--- /dev/null
+++ b/docs/quickstart.md
@@ -0,0 +1,88 @@
+# Quickstart
+
+This quickstart guide will walk you through the process of creating a set of
+Google OAuth credentials and using Docker Compose to run an example deployment
+of **sso** protecting two upstream services.
+
+
+## Prerequisites
+
+Before proceeding, ensure that the following software is installed:
+- [Git](https://help.github.com/articles/set-up-git/#setting-up-git)
+- [Docker](https://docs.docker.com/install/)
+- [Docker Compose](https://docs.docker.com/compose/install/)
+
+
+## 1. Clone this repo
+
+ git clone https://github.com/buzzfeed/sso.git
+
+The rest of this guide will assume you are in the `quickstart` subdirectory of
+the repo:
+
+ cd sso/quickstart
+
+
+## 2. Create Google OAuth Credentials
+
+Follow steps 1 and 2 of the [Google Provider Setup](google_provider_setup.md)
+documentation.
+
+**⚡️ Note:** Use `http://sso-auth.localtest.me/oauth2/callback` as the
+**Authorized redirect URI** in Step 2.
+
+At the end of step 2, you will have a client ID and client secret. Create a new
+file called `env` with those values, like so:
+
+ CLIENT_ID=.apps.googleusercontent.com
+ CLIENT_SECRET=
+
+This file will be used to configure `sso-auth` in the example deployment to
+allow you to log to **sso**.
+
+
+## 3. Create example `sso` deployment
+
+First, bring up all of the services:
+
+ docker-compose up -d
+
+Next, confirm that they are running as expected:
+
+ docker-compose ps
+
+You should see 5 services running, all in the "Up" state:
+
+ Name Command State Ports
+ --------------------------------------------------------------------------------------
+ quickstart_hello-world_1 /bin/sh -c php-fpm -d vari ... Up 80/tcp
+ quickstart_httpbin_1 /bin/go-httpbin Up 8080/tcp
+ quickstart_nginx-proxy_1 /app/docker-entrypoint.sh ... Up 0.0.0.0:80->80/tcp
+ quickstart_sso-auth_1 /bin/sso-auth Up 4180/tcp
+ quickstart_sso-proxy_1 /bin/sso-proxy Up 4180/tcp
+
+
+## 4. Explore your newly secured services!
+
+Visit http://hello-world.sso.localtest.me in your web browser. Log in using
+any Google account.
+
+Now visit http://httpbin.sso.localtest.me, and see that you are automagically
+logged in! (To verify that **sso** is actually working here, feel free to
+visit http://httpbin.sso.localtest.me in a private browsing window.)
+
+Make sure to take a look at http://httpbin.sso.localtest.me/headers to see the
+headers that **sso** provides to upstreams, like `X-Forwarded-User`,
+`X-Forwarded-Groups`, etc!
+
+**Note:** The `localtest.me` domain we use in this example deployment will always
+resolve to `127.0.0.1`, so it's a convenient way to avoid needing `/etc/hosts`
+hacks. See [readme.localtest.me](http://readme.localtest.me/) for more info.
+
+
+## Next Steps
+
+Take a look at [upstream_configs.yml](/quickstart/upstream_configs.yml) to see
+how `sso-proxy` is configured to protect these two upstream services, or check
+out [docker-compose.yml](/quickstart/docker-compose.yml) to see how the whole
+deployment is put together.
diff --git a/quickstart/docker-compose.yml b/quickstart/docker-compose.yml
new file mode 100644
index 00000000..64148fe8
--- /dev/null
+++ b/quickstart/docker-compose.yml
@@ -0,0 +1,143 @@
+version: '3'
+
+services:
+ # ===========================================================================
+ # sso services
+ #
+ # Here we provide a minimal sso installation for demo purposes that allows
+ # any valid google account to log in, and hard codes the various secrets
+ # required to secure communication.
+ #
+ # A more realistic deployment will likely require a specific organization's
+ # email domain and restrict access to upstream services based on Google Group
+ # membership.
+ #
+ # The sso-proxy service is handing requests to any domain under
+ # *.sso.localtest.me and the sso-auth service is available at sso-
+ # auth.localtest.me.
+ #
+ # There are two upstream services defined below, which can be accessed at
+ # - http://hello-world.sso.localtest.me
+ # - http://httpbin.sso.localtest.me
+ # ===========================================================================
+ sso-proxy:
+ image: buzzfeed/sso:latest # change this to `build: ..` to try local changes
+ entrypoint: /bin/sso-proxy
+ environment:
+ # Allow any google account to log in for demo purposes
+ - EMAIL_DOMAIN=*
+
+ - UPSTREAM_CONFIGS=/sso/upstream_configs.yml
+ - PROVIDER_URL=http://sso-auth.localtest.me
+
+ # CLIENT_ID and CLIENT_SECRET must match sso-auth's PROXY_CLIENT_ID and
+ # PROXY_CLIENT_SECRET configuration
+ - CLIENT_ID=aGNHd3FqWUVDb1Z0NVFVZDE4Vk8xbWhQeVdoc3pjMnU=
+ - CLIENT_SECRET=aDducXQzK2tPY3R4TmdqTGhaYS80eGYxcTUvWWJDb2M=
+
+ # XXX: These secrets are for demonstration purposes only! Use
+ #
+ # openssl rand -base64 32 | head -c 32 | base64
+ #
+ # to generate your own.
+ - AUTH_CODE_SECRET=SVM0NEFMUUlaZGxyaFVhOGxsQ0wvOFYyZTh2S2Fha1U=
+ - COOKIE_SECRET=WEl0Y054TXNUN2ltTWRkazZ0YmNpRTlucXBPQUY2VHU=
+
+ # Disable https for demo purposes
+ - COOKIE_SECURE=false
+
+ # TODO: these config values should probably have defaults
+ - CLUSTER=dev
+ - STATSD_HOST=127.0.0.1
+ - STATSD_PORT=8125
+
+ # Tells nginx-proxy service how to route requests to this service
+ - VIRTUAL_HOST=*.sso.localtest.me
+ volumes:
+ - ./upstream_configs.yml:/sso/upstream_configs.yml:ro
+ expose:
+ - 4180
+ # Because the sso-auth.localtest.me domain will not resolve correctly
+ # within the sso-proxy container for sso-proxy => sso-auth communication,
+ # we must manually add an /etc/hosts entry pointing at the docker-compose
+ # host IP
+ extra_hosts:
+ - 'sso-auth.localtest.me:172.20.0.1'
+
+ sso-auth:
+ image: buzzfeed/sso:latest # change this to `build: ..` to try local changes
+ entrypoint: /bin/sso-auth
+ env_file:
+ ./env
+ environment:
+ # Allow any google account to log in for demo purposes
+ - SSO_EMAIL_DOMAIN=*
+
+ - HOST=sso-auth.localtest.me
+ - REDIRECT_URL=http://sso-auth.localtest.me
+ - PROXY_ROOT_DOMAIN=localtest.me
+
+ # These values must match sso-proxy's CLIENT_ID and CLIENT_SECRET values
+ - PROXY_CLIENT_ID=aGNHd3FqWUVDb1Z0NVFVZDE4Vk8xbWhQeVdoc3pjMnU=
+ - PROXY_CLIENT_SECRET=aDducXQzK2tPY3R4TmdqTGhaYS80eGYxcTUvWWJDb2M=
+
+ # XXX: These secrets are for demonstration purposes only! Use
+ #
+ # openssl rand -base64 32 | head -c 32 | base64
+ #
+ # to generate your own.
+ - AUTH_CODE_SECRET=c1kxTHcyN3FwdGRiZHpZRU15TUpNdFlpb1ZEUUw5R3M=
+ - COOKIE_SECRET=V2JBZk0zWGtsL29UcFUvWjVDWWQ2UHExNXJ0b2VhcDI=
+
+ # Disable https for demo purposes
+ - COOKIE_SECURE=false
+
+ # TODO: these config values should probably have defaults
+ - CLUSTER=dev
+ - STATSD_HOST=127.0.0.1
+ - STATSD_PORT=8125
+
+ # TODO: remove the need for this config value
+ - OLD_COOKIE_SECRET=V2JBZk0zWGtsL29UcFUvWjVDWWQ2UHExNXJ0b2VhcDI=
+
+ # Tells nginx-proxy service how to route requests to this service
+ - VIRTUAL_HOST=sso-auth.localtest.me
+ expose:
+ - 4180
+
+ # ===========================================================================
+ # Upstream services protected by sso
+ #
+ # These services can be accessed at
+ # - hello-world.sso.localtest.me
+ # - httpbin.sso.localtest.me
+ # ===========================================================================
+ httpbin:
+ image: mccutchen/go-httpbin:latest
+ expose:
+ - 8080
+
+ hello-world:
+ image: tutum/hello-world:latest
+ expose:
+ - 80
+
+ # ===========================================================================
+ # nginx-proxy handles routing of requests to the sso-proxy and sso-auth
+ # containers. See its docs for more info:
+ # https://github.com/jwilder/nginx-proxy
+ # ===========================================================================
+ nginx-proxy:
+ image: jwilder/nginx-proxy:latest
+ ports:
+ - "80:80"
+ volumes:
+ - /var/run/docker.sock:/tmp/docker.sock:ro
+
+networks:
+ default:
+ driver: bridge
+ ipam:
+ driver: default
+ config:
+ - subnet: 172.20.0.0/16
diff --git a/quickstart/upstream_configs.yml b/quickstart/upstream_configs.yml
new file mode 100644
index 00000000..09917871
--- /dev/null
+++ b/quickstart/upstream_configs.yml
@@ -0,0 +1,9 @@
+- service: httpbin
+ default:
+ from: httpbin.sso.localtest.me
+ to: http://httpbin:8080
+
+- service: hello-world
+ default:
+ from: hello-world.sso.localtest.me
+ to: http://hello-world/