goat

package module
v0.0.0-...-6bfc58f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2020 License: MIT Imports: 12 Imported by: 0

README

Goat

Goat is a general purpose oauth2 access token server. It provides a way for servers and clients to securely maintain Oauth refresh tokens.

The typical flow is as follows:

  1. A browser client makes an authenticated HTTP request to Goat, requesting credentials for a specific Oauth provider (such as "google"). This provider must be configured within Goat with the Oauth credentials. In addition this oauth provider must be configured with redirect URLs to Goat.

  2. Goat checks if it has the oauth credentials for the current authenticated user. If it does, it responds with an oauth Token JSON (but without the refresh_token part). Note: if the token stored with Goat is expired, it refreshes it automatically.

  3. Goat returns an empty hash if it doensn't have the credentials. In this case, the browser client needs to initiate an Oauth "consent" flow with the oauth provider. The browser client redirects the user to the Goat server consent endpoint for that provider.

  4. Goat calculates a nonce to prevent CSRF attacks, saves the redirect URL to the session store and then redirects the browser to the actual consent page of the oauth provider.

  5. When the user completes the consent flow of the oauuth provider, the oauth provider redirects to the configured redirect URL, which should be the "auth code" endpoint on the Goat server.

  6. The Goat server handles the auth code provided by the oauth provider and exchanges it to get the token. It encrypts this token using an encryption engine and then saves it within the token storage. It then redirects the browser back to the redirect URL configured by the client in step 3.

  7. The client detects that the consent flow has completed successfully and then initiates step 1 again, which will now succeed.

Goat integration

Goat requires a session storage, a token storage and an encryption engine.

A Redis-based session storage engine is implemented in the ./sessions package.

A entgo.io based token storage is implemented in the ./tokens package.

A vault based encryption engine is implemented in the ./secrets package (where vault is used as a secrets transit engine rather than actual storage).

All of these are optional with custom implementations possible. An example which pulls these together is provided along with its assocciated config

This example requires a local redis and a local vault. The token storage uses an in-memory sqllite3 DB. The example and the associated redis/vault containers can be launched via the ./scripts/run.sh script.

For the example to work, the config.yaml.sample file must be updated to specify a valid provider and then one should visit the following url in the browser http://localhost:8085/sheets/url?redirect_url=http://www.google.com.

The URL above should walk through the consent flow and end up on google. At this point, the current token can be fetched by visiting this url

Sample Config file

The following is a sample config file for this service:

httpport: 8085
providers:
  - name: sheets
    paths:
      consent: /sheets/url
      code: /sheets/code
      setrefreshtoken: /sheets/setRefreshToken
      getaccesstoken: /sheets/token
    config:
      endpoint:
        authurl: https://accounts.google.com/o/oauth2/auth
        tokenurl: https://oauth2.googleapis.com/token
      clientid: <your google API project client_id>
      clientsecret: <your google API project client_secret>
      redirecturl: http://localhost:8085/sheets/code
      scopes: ["email"]
tokens:
  dbsource: "file:ent?mode=memory&cache=shared&_fk=1"
  dbtype: sqlite3
sessions:
  ttl: 30s
  options:
    addr: "localhost:6379"
secrets:
  encryptpath: transit/encrypt/goat
  decryptpath: transit/decrypt/goat

Running the tests

Tests can be run locally via the ./scripts/test.sh script (which launches the required docker images and such.

The following manual process can be used to setup the vault container:

docker run --rm --cap-add=IPC_LOCK -e VAULT_DEV_ROOT_TOKEN_ID=hello --name=dev-vault -p 8200:8200 vault:1.5.0

In addition, the example expects vault to be configured as a transit engine and a keyring:

docker exec -it $(docker ps -q -f name=dev-vault) sh -c 'VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN=hello vault secrets
enable transit'
docker exec -it $(docker ps -q -f name=dev-vault) sh -c 'VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN=hello vault write -f transit/keys/goat'

Now the tests can be run using:

VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN=hello go test ./...

Example

See this example server for how to set up your service.

Documentation

Overview

Package goat implements a native Go oauth access token server.

The expected flow for this server:

1. Client requests an access token for a provider, say, "google", for the current user.

2. The goat server returns an access token (or an empty string if one isn't available)

3. If an access token is not available, the client sends the browser to /google/url?redirect_url=something on the goat server.

4. The goat server redirects to the consent page for the provider ("google"). Once consent succeeds, the server then redirects the browser to the redirect URL configured for the provider which souuld be the goat server.

5. When redirected from the provider, the goat server gets the access token via standard oauth2 flows. This is then saved to its secure storage. The goat server than redirects to the URI provided in step3.

6. The client gets the redirect to this page and starts with step 1 which shoudl now succeed.

Session & Secure Storage

The goat server requries three components for security:

1. A session storage to hold the state parameter to prevent CSRF attacks.

2. An token storage to store encrypted tokens.

3. An encrpyption engine to encrypt/decrypt tokens.

Example implementations for these are available in sub-directories.

An example that puts this together is available in the cmd/goat example.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type EncrypterDecrypter

type EncrypterDecrypter interface {
	Encrypt(ctx context.Context, data []byte) ([]byte, error)
	Decrypt(ctx context.Context, data []byte) ([]byte, error)
}

EncrypterDecrypter is the interface to be implemented by an encryption engine.

type Handler

type Handler struct {
	// Providers contains the list of providers.
	Providers []*Provider

	// AuthenticatedUser is a function that checks that the
	// request is authenticated and gets the identity of the
	// requester.  All Goat requests must be authenticated.
	AuthenticatedUser func(r *http.Request) (string, error)

	// Tokens is the token storage.  All the tokens are encrypted
	// before-hand and so this does not require special handling
	// for security.
	Tokens TokenStore

	// Sessions is used to take a random nonce to prevent CSRF
	// attacks.  It is a transient store.
	Sessions SessionStore

	// Secrets provides the encryption/description support.
	Secrets EncrypterDecrypter
}

Handler is a http.Handler that serves all the http requests.

func (Handler) Handle

func (h Handler) Handle(r *http.Request) (body interface{}, err error, status int)

Handle does all the work for handling HTTP requests but does not respond. It returns the response for the caller to respond.

For redirects, the body contains the URL to be redirected to.

func (Handler) ServeHTTP

func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler. If better control over error handling and logging is required, please directly call Handle which does not write any responses other than redirect.

type Paths

type Paths struct {
	// Consent specifies the path for consent requests. These get
	// redirected to the aouth2 provider's auth URL
	//
	// Requests to the consent URL require a redirect_url
	// parameter that is used to redirect to after completion of
	// the full consent flow.
	Consent string

	// Code specifies the path configured as the RedirectURL with
	// the oauth provider.  This is expected to contain the code
	// that can be exchanged for the auth token.
	Code string

	// SetRefreshToken specifies the path where SetRefreshToken
	// requests happen. The refresh_token parameter (query or form
	// value) should hold the actual refresh token.  This is first
	// validated and so must be a real refresh token.
	SetRefreshToken string

	// GetAccessToken specifies the path where the acess token can
	// be fetched from. The response is a JSON with valid
	// refresh_token field or an empth hash (indicating no
	// credentials available).
	GetAccessToken string
}

Paths contains the paths for a single provider.

type Provider

type Provider struct {
	// Name of the provider.  Used with TokenStore & SessionStore
	Name string

	// Config should specify endpoints and such
	oauth2.Config

	// UsePKCE specifies that the provider users PKCE.
	UsePKCE bool

	// Paths should specify the endpoints for the Goat
	// server. This should be unique and not shared between
	// providers.
	Paths Paths

	// AuthURLParams specifies additional auth url options.
	// Offline access is included automatically.
	AuthURLParams map[string]string
}

Provider specifies a specific oauth provider + associated config.

type SessionStore

type SessionStore interface {
	Get(ctx context.Context, provider, nonce string) ([]byte, error)
	Set(ctx context.Context, provider, nonce string, session []byte) error
}

SessionStore is the interface a session store must implement. It is a simple key value store.

Note:

The session store should expire the values quickly (1m is recommended).

The sesion store should also delete the keys once they are fetched. That is, all Get calls should effectively also delete immediately.

type TokenStore

type TokenStore interface {
	Get(ctx context.Context, provider, user string) ([]byte, error)
	Set(ctx context.Context, provider, user string, token []byte) error
}

TokenStore is the interface a token store must implement. It is a simple key value store.

Directories

Path Synopsis
cmd
goat
This command demonstrates how to run a goat server.
This command demonstrates how to run a goat server.
Package secrets implements goat secret encryption/decryption.
Package secrets implements goat secret encryption/decryption.
Package sessions implements a session store for goat.
Package sessions implements a session store for goat.
ent

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL