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/