chronograf/docs/auth.md

5.0 KiB

Chronograf with OAuth 2.0 (Github-style)

OAuth 2.0 Style Authentication

Configuration

To use authentication in Chronograf, both Github OAuth and JWT signature need to be configured.

Creating Github OAuth Application

To create a Github OAuth Application follow the Register your app instructions. Essentially, you'll register your application here

The Homepage URL should be Chronograf's full server name and port. If you are running it locally for example, make it http://localhost:8888

The Authorization callback URL must be the location of the Homepage URL plus /oauth/github/callback. For example, if Homepage URL was http://localhost:8888 then the Authorization callback URL should be http://localhost:8888/oauth/github/callback.

Github will provide a Client ID and Client Secret. To register these values with chronograf set the following environment variables:

  • GH_CLIENT_ID
  • GH_CLIENT_SECRET

For example:

export GH_CLIENT_ID=b339dd4fddd95abec9aa
export GH_CLIENT_SECRET=260041897d3252c146ece6b46ba39bc1e54416dc

Configuring JWT signature

Set a JWT signature to a random string. Keep this random string around!

You'll need it each time you start a chronograf server because it is used to verify user authorization. If you are running multiple chronograf servers in an HA configuration set the TOKEN_SECRET on each to allow users to stay logged in.

export TOKEN_SECRET=supersupersecret

Optional Github Organizations

To require an organization membership for a user, set the GH_ORGS environment variables

export GH_ORGS=biffs-gang

If the user is not a member, then the user will not be allowed access.

To support multiple organizations use a comma delimted list like so:

export GH_ORGS=hill-valley-preservation-sociey,the-pinheads

Design

The Chronograf authentication scheme is a standard web application OAuth flow.

oauth 2.0 flow

The browser receives a cookie from Chronograf, authorizing it. The contents of the cookie is a JWT whose "sub" claim is the user's primary github email address.

On each request to Chronograf, the JWT contained in the cookie will be validated against the TOKEN_SECRET signature and checked for expiration. The JWT's "sub" becomes the principal used for authorization to resources.

The API provides three endpoints /oauth, /oauth/logout and /oauth/github/callback.

/oauth

The /oauth endpoint redirects to Github for OAuth. Chronograf sets the OAuth state request parameter to a JWT with a random "sub". Using $TOKEN_SECRET /oauth/github/callback can validate the state parameter without needing state to be saved.

/oauth/github/callback

The /oauth/github/callback receives the OAuth authorization code and state.

First, it will validate the state JWT from the /oauth endpoint. JWT validation only requires access to the signature token. Therefore, there is no need for state to be saved. Additionally, multiple Chronograf servers will not need to share third party storage to synchronize state. If this validation fails, the request will be redirected to /login.

Secondly, the endpoint will use the authorization code to retrieve a valid OAuth token with the user:email scope. If unable to get a token from Github, the request will be redirected to /login.

Finally, the endpoint will attempt to get the primary email address of the Github user. Again, if not successful, the request will redirect to /login.

The email address is used as the subject claim for a new JWT. This JWT becomes the value of the cookie sent back to the browser. The cookie is valid for thirty days.

Next, the request is redirected to /.

For all API calls to /chronograf/v1, the server checks for the existence and validity of the JWT within the cookie value. If the request did not have a valid JWT, the API returns HTTP/1.1 401 Unauthorized.

/oauth/logout

Simply expires the session cookie and redirects to /.

Authorization

After successful validation of the JWT, each API endpoint of /chronograf/v1 receives the JWT subject within the http.Request as a context.Context value.

Within the Go API code all interfaces take context.Context. This means that each interface can use the value as a principal. The design allows for authorization to happen at the level of design most closely related to the problem.

An example usage in Go would be:

func ShallIPass(ctx context.Context) (string, error) {
    principal := ctx.Value(chronograf.PrincipalKey).(chronograf.Principal)
    if principal != "gandolf@moria.misty.mt" {
        return "you shall not pass", chronograf.ErrAuthentication
    }
    return "run you fools", nil
}