Auth0 is an OpenID Connect compliant OAuth2 provider, so we're able to
re-use the generic OAuth2 provider to implement it. The routes required
by Auth0 have been hardcoded for user convenience.
Also, Auth0 requires users to register a subdomain of auth0.com when
signing up. This must be provided to chronograf through the
`--auth0-domain` parameter (or `AUTH0_DOMAIN` ENV). This is **distinct**
from the `PUBLIC_URL`. For example, for a Chronograf hosted at
`http://www.example.com`, and an Auth0 domain of
`http://oceanic-airlines.auth0.com`, a client-id of `notpennysboat` and a
client-secret of `4-8-15-16-23-42`, the command line options would look
like:
```
chronograf \
--auth0-domain=http://oceanic-airlines.auth0.com \
--auth0-client-id=notpennysboat \
--auth0-secret=4-8-15-16-23-24
--public-url=http://www.example.com
-t `uuidgen`
```
When using a basepath of /chronograf, the app would present a
never-ending spinner when visiting the root route. This was because the
prefixingRedirector middleware which is responsible for appending the
basepath to redirects from downstream http.Handlers thought that the
prefix was already appended since it saw `/chronograf/v1`. In reality,
it should have produced a location like `/chronograf/chronograf/v1`.
The solution was to look beyond the first instance of a prefix and check
for the presence of another prefix to detect if a prefix was already
applied by a downstream handler.
The Basepath option should be applied in anything that will be consumed
by the React application. This is because from its perspective, the
proxy sitting between it and the backend wants those prefixes regardless
of what it does with them before handing the request back to the
Chronograf backend. Consequently, there's situations in the backend
where we need to have the `opts.Basepath` or the `basepath` that we
alter when `opts.PrefixRoutes` is set. The `basepath` is strictly for
altering routing decisions made by the backend.
There's subtle places where routes are supplied to the frontend that
need to always have the `opts.Basepath` set as well. Another commit
addressed the "Location" header of Redirects, for example.
The router that we use has a feature that will automatically redirect
routes in certain situations where it feels a trailing slash would be
appropriate. Because the underlying router is totally unaware of
upstream prefixing activity, the "Location" that it sends clients to is
incorrect because it doesn't have the prefix.
This introduces a middleware that catches any downstream 3XX class
responses and replaces the Location header with the prefixed version of
it, plus a trailing slash. It does this only when the prefix has not
been applied already by some downstream middleware.
This adds the status code to the response log message to make it easier
to diagnose issues. It also replaces the placeholder "Success" message
with the decoded value of the HTTP Status, resulting in messages like:
INFO[0041] Response: Temporary Redirect code=307
...and so on. Both easily consumable by humans and machines.
Basepath was previously not working here because the strings constructed
via concatenation had a trailing slash at the end:
Before:
rootPath => "/someprefix/chronograf/v1/"
After:
rootPath => "/someprefix/chronograf/v1"
The julienschmidt/httprouter that the bouk/httprouter is based on has
support for ignoring trailing slashes, which is behavior that we want.
However, routing decisions involving this rootPath string were being
made by a `strings.HasPrefix` function. This conditional seeks to
apply the token middleware only in cases where routes _under_
`/chronograf/v1` are accessed (e.g. `/chronograf/v1/sources`). In cases
where the paths were effectively equal, this conditional accidentally
worked because the string `/chronograf/v1` does not have the prefix
`/chronograf/v1/`. When this was corrected to use `path.Join`, this case
became true and caused the token middleware to be applied.
`path.Join` is the correct way to construct paths, since this prevents
issues where a fragment like `/foo/` is concatenated with a fragment
like `/bar/quux/` to yield the string `/foo//bar/quux/`.
Given that continuing to use concatenation is no longer an option, the
solution is to compare the lengths of the strings to ensure that the
path under comparison is longer than the prefix it's being tested
against. This guarantees that the subject path is a route underneath the
`/chronograf/v1` route.
Updated the logout link in the UI to use a link provided by the
/chronograf/v1/ endpoint. We also replaced many instances of string
concatenation of URL paths with path.Join, which better handles cases
where prefixed and suffixed "/" characters may be present in provided
basepaths. We also refactored how Basepath was being prefixed when using
Auth. Documentation was also updated to warn users that basepaths should
be applied to the OAuth callback link when configuring OAuth with their
provider.
JWTs will only life five minutes into the future. Any time
the server receives an authenicated request, the JWT's expire at
will be extended into the future.
* User can now set oauth cookie session duration via the CLI to any duration or to expire on browser close
* Refactor GET 'me' into heartbeat at constant interval
* Add ping route to all routes
* Add /chronograf/v1/ping endpoint for server status
* Refactor cookie generation to use an interface
* WIP adding refreshable tokens
* Add reminder to review index.js Login error handling
* Refactor Authenticator interface to accommodate cookie duration and logout delay
* Update make run-dev to be more TICKStack compliant
* Remove heartbeat/logout duration from authentication
* WIP Refactor tests to accommodate cookie and auth refactor
* Update oauth2 tests to newly refactored design
* Update oauth provider tests
* Remove unused oauth2/consts.go
* Move authentication middleware to server package
* Fix authentication comment
* Update authenication documentation to mention AUTH_DURATION
* Update /chronograf/v1/ping to simply return 204
* Fix Makefile run-dev target
* Remove spurious ping route
* Update auth docs to clarify authentication duration
* Revert "Refactor GET 'me' into heartbeat at constant interval"
This reverts commit 298a8c47e1.
Conflicts:
ui/src/index.js
* Add auth test for JWT signing method
* Add comments for why coverage isn't written for some areas of jwt code
* Update auth docs to explicitly mention how to require re-auth for all users on server restart
* Add Duration to Validation interface for Tokens
* Make auth duration of zero yield a everlasting token
* Revert "Revert "Refactor GET 'me' into heartbeat at constant interval""
This reverts commit b4773c15af.
* Rename http status constants and add FORBIDDEN
* Heartbeat only when logged in, notify user if heartbeat fails
* Update changelog
* Fix minor word semantics
* Update oauth2 tests to be in the oauth2_test package
* Add check at compile time that JWT implements Tokenizer
* Rename CookieMux to AuthMux for consistency with earlier refactor
* Fix logout middleware
* Fix logout button not showing due to obsolete data shape expectations
* Update changelog
* Fix proptypes for logout button data shape in SideNav
Re-mounting should only happen if the --prefix-routes option is set. If
this happens, the result will be a no-op as intended since the
--basepath will be "". MountableRouter and http.StripPrefix are both
no-ops with prefix set to ""
http.StripPrefix is a standard library handler which is designed to do
exactly what the inline http.HandlerFunc did (with almost the same
implementation).
In certain situations, the http.ResponseWriter passed to the URLPrefixer
may not be an http.Flusher. A simple case where this may occur is if the
Prefixer has been wrapped up in a middleware where the above middleware
wraps the ResponseWriter in a ResponseWriter that doesn't implement the
Flush method.
Previously, the Prefixer would error, which would cause the request to
fail with a 500. Instead, the condition is logged and the request is
passed unmodified to the next middleware in the chain. This effectively
disables prefixing for requests where the above ResponseWriter is not an
http.Flusher.
Misc. Changes
=============
- Some tests for "builders" were moved to server/builders_test.go to
follow with convention. We've been naming files after different things
under test and leaving the file matching the package name for support
objects-in this case a mock logger was added to that file.
Some load balancers will strip prefixes on their way to the chronograf
backend, others won't. The "--prefix-routes" parameter forces all
requests to the backend to have the prefix specified in "--basepath".
Omitting it will only cause routes to be rewritten in rendered
templates and assumes that the load balancer will remove the prefix.
Use with Caddy
==============
An easy way to test this out is using the free Caddy http server at
http://caddyserver.com.
This Caddyfile will work with the options `--basepath /chronograf
--prefix-routes` set:
```
localhost:2020 {
proxy /chronograf localhost:8888
log stdout
}
```
This Caddyfile will work with only the option `--basepath /chronograf`
set:
```
localhost:2020 {
proxy /chronograf localhost:8888 {
except /chronograf
}
log stdout
}
```
This breaks compatibility with the old behavior of --basepath, so this
requires that proxies be configured to not modify routes forwarded to
backends. The old behavior will be supported in a subsequent commit.
The httprouter used in Chronograf did not support prefixing every route
with some basepath. This caused problems for those using the --basepath
parameter in combination with a load balancer that did not strip the
basepath prefix from requests that it forwarded onto Chronograf.
To support this, MountableRouter prefixes all routes at definition time
with the supplied prefix.
* Introduce Kapacitor and InfluxDB as command line options
If omitted, their values will be null at runtime. If supplied, e.g.:
chronograf
--kapacitor https://path.to.my:1/kapacitor/instance
--influxdb https://path.to.my:1/influxdb/instance
Their values will be accessible via
Server.Kapacitor
Server.InfluxDB
* MultiSourcesStore will hold Bolt and config’d sources
* Delegate to db.SourcesStore for now
* Add Username/Password tags for InfluxDB and Kapacitor
* Builders for MultiSourceStore and MultiLayoutStore
* Store Kapacitor and InfluxDB configs in memory
* Typo
* Update CHANGELOG
* Move StoreBuilders to server/builders.go
* Correct these assertions by reversing them
* Kapacitor -> KapacitorURL; InfluxDB -> InfluxDBURL