Skip to content

Commit

Permalink
Fix oauth2 forwarding (#77)
Browse files Browse the repository at this point in the history
## Motivation

In testing 2.1.1, I found that the forwarding did not work correctly when using `auth_mode: openidconnect`. Specifically, loading the main page would result in loading keycloak. These changes improve the `openidconnect` auth mode and ensure that the other auth modes still work.

## Changes

* Simplify the `oauth2` configuration. Since we haven't actually released any of these configurations, I am changing them now.
* Add the `openid` scope to the browser's request, since it is expected by some providers, like Okta.
* Load only one of the `/api/v1/auth/token` endpoints.

## Verifying the change
I verified these changes with all 4 auth modes an EKS cluster and against the DataStax Pulsar Helm Chart.
  • Loading branch information
michaeljmarshall authored Jun 24, 2022
1 parent 574cb1f commit 636d1ba
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 48 deletions.
13 changes: 5 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,9 @@ In a geo-replication configuration, you will want to use the cluster name for th
| server_config.kubernetes.service_port | | When using `k8s` auth_mode, specify a custom Kubernetes port. |
| server_config.user_auth.username | | When using `user` auth_mode, the login user name. |
| server_config.user_auth.password | | When using `user` auth_mode, the login password. |
| server_config.oauth2.enabled | | When using `openidconnect` set to `true` to forward token requests. |
| server_config.oauth2.hostname | | When using `openidconnect` set to your hostname ex: `localhost` |
| server_config.oauth2.forwardingPath | | When using `openidconnect` set to the path you need to forward to to get the token |
| server_config.oauth2.enableTls | | When using `openidconnect` set to `true` if you wish to use `HTTPS` |
| server_config.oauth2.http | | When using `openidconnect` and only using `HTTP` set to your port |
| server_config.oauth2.https | | When using `openidconnect` and using `HTTPS` set to your port |
| server_config.oauth2.identity_provider_url | `""` | When using `auth_mode: openidconnect` set to your hostname and port. ex: `https://keycloak:8443`|
| server_config.oauth2.token_endpoint | `""` | When using `auth_mode: openidconnect` set to the path you need to forward to to get the token. ex: `/token` |
| server_config.oauth2.grant_type | `password` | When using `auth_mode: openidconnect` set to the grant type. Only `password` is support at this time. |
| polling_interval | 10000 | How often the console polls Pulsar for updated values. In milliseconds. |
| ca_certificate | | String of CA certificate to display in the console under Credentials. |
| api_version | 2.8.3 | Version of the Pulsar client API to recommend under Samples. |
Expand Down Expand Up @@ -117,8 +114,8 @@ Once the user is authenticated using one of the Kubernetes secrets, the token fo

### Auth Mode: OpenID Connect
In this auth mode, the dashboard will use your login credentials to attempt to retrieve a JWT from an authentication
provider. In the [DataStax Pulsar Helm Chart](https://github.com/datastax/pulsar-helm-chart), this is implemented by
integrating the Pulsar Admin Console with Keycloak. Upon successful retrieval of the JWT, the Pulsar Admin Console will
provider by following the `password` grant type. In the [DataStax Pulsar Helm Chart](https://github.com/datastax/pulsar-helm-chart), this is implemented by
integrating the Pulsar Admin Console with an identity provider, like Keycloak. Upon successful retrieval of the JWT, the Pulsar Admin Console will
use the retrieved JWT as the bearer token when making calls to Pulsar.

In addition to configuring the `auth_mode`, you also need to configure the `oauth_client_id`. This is the client id that
Expand Down
11 changes: 4 additions & 7 deletions config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"auth_mode": "none",
"cluster_name": "standalone",
"tenant": "public",
"oauth_client_id": "console",
"oauth_client_id": "",
"server_config": {
"port": "6454",
"pulsar_url": "http://localhost:8080",
Expand All @@ -28,12 +28,9 @@
"password": ""
},
"oauth2": {
"enabled": false,
"hostname": "",
"forwardingPath": "/token",
"enableTls": false,
"httpPort": "",
"httpsPort": ""
"identity_provider_url": "",
"token_endpoint": "",
"grant_type": "password"
}
},
"polling_interval": "10000",
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/components/auth/login/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export function getPulsarToken (accessToken) {
function buildLoginBody(username, password) {
if (globalConf.auth_mode === 'openidconnect') {
return new URLSearchParams({
scope: 'openid',
username: username,
password: password,
client_id: globalConf.oauth_client_id,
Expand Down
60 changes: 27 additions & 33 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,23 +192,14 @@ app.use(`/api/v1/${cluster}/sources`, createProxyMiddleware({
selfHandleResponse: true
}));

const keycloakTarget = cfg.globalConf.server_config.oauth2.enableTls ?
`https://${cfg.globalConf.server_config.oauth2.hostname}:${cfg.globalConf.server_config.oauth2.httpsPort}` :
`http://${cfg.globalConf.server_config.oauth2.hostname}:${cfg.globalConf.server_config.oauth2.httpPort}`

app.use(createProxyMiddleware({
target: keycloakTarget,
pathFilter: (path, req) => {
if (cfg.globalConf.server_config.oauth2.enabled && path.includes('/api/v1/auth/token')) {
return true;
}
return false;
},
pathRewrite: (path, req) => {
return path.replace('/api/v1/auth/token', cfg.globalConf.server_config.oauth2.forwardingPath)
},
secure: cfg.globalConf.server_config.ssl.verify_certs,
}))
if (cfg.globalConf.auth_mode === 'openidconnect' && cfg.globalConf.server_config.oauth2.grant_type === 'password') {
app.use('/api/v1/auth/token', createProxyMiddleware({
target: cfg.globalConf.server_config.oauth2.identity_provider_url,
pathFilter: '/api/v1/auth/token',
pathRewrite: {'^/api/v1/auth/token': cfg.globalConf.server_config.oauth2.token_endpoint},
changeOrigin: true, // By changingOrigin, we're able to make internal k8s networking work for the token
}))
}

app.use(`/api/v1/${cluster}`, createProxyMiddleware({
target: cfg.globalConf.server_config.pulsar_url,
Expand Down Expand Up @@ -284,24 +275,27 @@ app.post('/api/user', (req, res) => {
res.json("user addedd");
});

app.post('/api/v1/auth/token', async (req, res) => {
const username = req.body.username;
const password = req.body.password;
try {
if (username && password) {
if (await isUserAuthenticated(username, password)) {
const secret = process.env.TOKEN_SECRET || cfg.globalConf.server_config.token_secret || "default-secret"
// This loosely complies with https://openid.net/specs/openid-connect-core-1_0.html section 3.2.2.5. Successful Authentication Response
res.send({access_token: jwt.sign({user: username}, secret, {expiresIn: '12h'})});
return;
// OpenID Connect has a different model defined above.
if (cfg.globalConf.auth_mode !== 'openidconnect') {
app.post('/api/v1/auth/token', async (req, res) => {
const username = req.body.username;
const password = req.body.password;
try {
if (username && password) {
if (await isUserAuthenticated(username, password)) {
const secret = process.env.TOKEN_SECRET || cfg.globalConf.server_config.token_secret || "default-secret"
// This loosely complies with https://openid.net/specs/openid-connect-core-1_0.html section 3.2.2.5. Successful Authentication Response
res.send({access_token: jwt.sign({user: username}, secret, {expiresIn: '12h'})});
return;
}
}
res.status(401).send("incorrect credentials");
} catch (e) {
cfg.L.error(e);
res.status(401).send("login exception");
}
res.status(401).send("incorrect credentials");
} catch (e) {
cfg.L.error(e);
res.status(401).send("login exception");
}
});
});
}

app.post('/auth', async (req, res) => {
const username = req.body.username;
Expand Down

0 comments on commit 636d1ba

Please sign in to comment.