Fix incorrect solution for automatic browser launch in OIDC login flow.
This commit is contained in:
1 parent
5f8c65674c
commit
065bf2eddd
2 files changed
+7
-99
No files matched your search
@@ -7,7 +7,7 @@ This guide shows how an administrator configures the Vault OIDC authentication m
|
||||
- The Vault PKI configured as described in step 1a of the main `README.md`: the `pki_int` mount and the `wif-client` role (`vault_configure.sh`).
|
||||
- A Vault server reachable over HTTPS. The examples use `https://vault.example.com:8200`.
|
||||
- Permissions to register applications in the Entra ID tenant.
|
||||
- `vault` and `openssl` on the workstation of the administrator and of the user, Node.js 24 or later and a web browser on the workstation of the user.
|
||||
- `vault` and `openssl` on the workstation of the administrator and of the user, and a web browser on the workstation of the user.
|
||||
|
||||
The commands are written for bash.
|
||||
|
||||
@@ -105,23 +105,23 @@ The CLI cannot express the `bound_claims` map as `key=value` arguments, which is
|
||||
|
||||
Role: **User**
|
||||
|
||||
1. Set the address of the Vault server. Set `VAULT_CACERT` and `NODE_EXTRA_CA_CERTS` as well when the server certificate is issued by a private CA. `vault_oidc_login.mjs` runs on Node.js, which does not read `VAULT_CACERT`.
|
||||
1. Set the address of the Vault server. Set `VAULT_CACERT` as well when the server certificate is issued by a private CA.
|
||||
|
||||
```bash
|
||||
export VAULT_ADDR=https://vault.example.com:8200
|
||||
export VAULT_CACERT=/path/to/server-ca.pem
|
||||
export NODE_EXTRA_CA_CERTS=/path/to/server-ca.pem
|
||||
```
|
||||
|
||||
2. Log in with the Entra ID account using `vault_oidc_login.mjs`. The script does not launch a browser. It prints the Entra ID sign-in URL, waits on `localhost:8250` for the redirect, confirms the login with the policies and the lifetime of the token, and prints the Vault token. The messages go to the standard error output, so the command substitution below receives only the token.
|
||||
2. Unset `VAULT_TOKEN`, because a token in this environment variable takes precedence over the token stored by `vault login` and the CLI would keep using it. Then log in with the Entra ID account. The `skip_browser=true` parameter stops the CLI from launching the browser. The CLI prints the Entra ID sign-in link instead, waits on `localhost:8250` for the redirect, and after the sign-in stores the token in the token helper file (`~/.vault-token`).
|
||||
|
||||
```bash
|
||||
export VAULT_TOKEN=$(node vault_oidc_login.mjs --role entra-pki)
|
||||
unset VAULT_TOKEN
|
||||
vault login -method=oidc skip_browser=true role=entra-pki
|
||||
```
|
||||
|
||||
Open the printed URL in the browser, or in the browser profile or the private window, signed in with the account that is a member of the group. Entra ID signs in with the session that the browser already has, without asking. A token of another account fails with `claim "groups" does not match any associated bound claim values`, because that account is not a member of the group.
|
||||
Open the printed link in the browser, or in the browser profile or the private window, signed in with the account that is a member of the group. Entra ID signs in with the session that the browser already has, without asking. A token of another account fails with `claim "groups" does not match any associated bound claim values`, because that account is not a member of the group.
|
||||
|
||||
The script has the optional `--mount` (default `oidc`), `--redirect-uri` (default `http://localhost:8250/oidc/callback`) and `--timeout` (default `300` seconds) options. The redirect URI must be registered in Entra ID and listed in `allowed_redirect_uris` of the role.
|
||||
The optional `callbackhost`, `callbackport`, `listenaddress` and `mount` parameters change the callback listener. The redirect URI built from them must be registered in Entra ID and listed in `allowed_redirect_uris` of the role.
|
||||
|
||||
3. Generate the client key and the CSR locally. The key is never sent to Vault.
|
||||
|
||||
@@ -153,7 +153,6 @@ Role: **User**
|
||||
|
||||
```bash
|
||||
vault token revoke -self
|
||||
unset VAULT_TOKEN
|
||||
```
|
||||
|
||||
The user token can only sign with the `wif-client` role. Reading the role or any other path returns `permission denied`.
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { createServer } from "node:http";
|
||||
import { parseArgs } from "node:util";
|
||||
|
||||
const { values } = parseArgs({
|
||||
options: {
|
||||
role: { type: "string" },
|
||||
mount: { type: "string", default: "oidc" },
|
||||
"redirect-uri": { type: "string", default: "http://localhost:8250/oidc/callback" },
|
||||
timeout: { type: "string", default: "300" },
|
||||
},
|
||||
});
|
||||
const address = process.env.VAULT_ADDR;
|
||||
|
||||
if (!values.role || !address) {
|
||||
console.error(
|
||||
"Usage: VAULT_ADDR=... vault_oidc_login.mjs --role ROLE [--mount MOUNT] [--redirect-uri URI] [--timeout SECONDS]",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const redirect = new URL(values["redirect-uri"]);
|
||||
const clientNonce = randomBytes(16).toString("hex");
|
||||
const base = `${address}/v1/auth/${values.mount}/oidc`;
|
||||
|
||||
// Ask Vault for the sign-in URL of the identity provider.
|
||||
const authResponse = await fetch(`${base}/auth_url`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
role: values.role,
|
||||
redirect_uri: values["redirect-uri"],
|
||||
client_nonce: clientNonce,
|
||||
}),
|
||||
});
|
||||
if (!authResponse.ok) {
|
||||
console.error(`Vault returned ${authResponse.status}: ${await authResponse.text()}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const { data } = await authResponse.json();
|
||||
|
||||
console.error(`Open this URL in the browser or in the browser profile of your choice:\n\n ${data.auth_url}\n`);
|
||||
console.error("Waiting for the sign-in to complete...");
|
||||
|
||||
// Wait for the identity provider to redirect the browser to the local callback.
|
||||
const params = await new Promise((resolve, reject) => {
|
||||
const server = createServer((request, response) => {
|
||||
const url = new URL(request.url, redirect);
|
||||
if (url.pathname !== redirect.pathname) {
|
||||
response.writeHead(404).end();
|
||||
return;
|
||||
}
|
||||
response.writeHead(200, { "Content-Type": "text/plain", Connection: "close" });
|
||||
response.end("The sign-in is complete, you can close this window.\n");
|
||||
server.close();
|
||||
server.closeAllConnections();
|
||||
resolve(url.searchParams);
|
||||
});
|
||||
server.listen(Number(redirect.port), redirect.hostname);
|
||||
setTimeout(() => {
|
||||
server.close();
|
||||
server.closeAllConnections();
|
||||
reject(new Error("Timed out waiting for the sign-in"));
|
||||
}, Number(values.timeout) * 1000).unref();
|
||||
}).catch((error) => {
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
if (params.get("error")) {
|
||||
console.error(`The identity provider returned ${params.get("error")}: ${params.get("error_description")}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Let Vault exchange the authorization code and issue the token.
|
||||
const callback = new URLSearchParams({
|
||||
state: params.get("state"),
|
||||
code: params.get("code"),
|
||||
client_nonce: clientNonce,
|
||||
});
|
||||
const callbackResponse = await fetch(`${base}/callback?${callback}`);
|
||||
if (!callbackResponse.ok) {
|
||||
console.error(`Vault returned ${callbackResponse.status}: ${await callbackResponse.text()}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const { auth } = await callbackResponse.json();
|
||||
console.error(
|
||||
`Login successful. Policies: ${auth.policies.join(", ")}. The token is valid for ${auth.lease_duration} seconds.`,
|
||||
);
|
||||
console.log(auth.client_token);
|
||||
Reference in new issue
Block a user