178 lines
7.3 KiB
Markdown
178 lines
7.3 KiB
Markdown
# Requesting a client certificate from a remote Vault server with Microsoft Entra ID authentication
|
|
|
|
This guide shows how an administrator configures the Vault OIDC authentication method with Microsoft Entra ID as the identity provider, and how a user logs in with the Entra ID account to a remote Vault server and gets a client certificate signed by the Vault CA.
|
|
|
|
## Prerequisites
|
|
|
|
- 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.
|
|
|
|
The commands are written for bash.
|
|
|
|
## 1. Register the application in Entra ID
|
|
|
|
Role: **Administrator**
|
|
|
|
1. Register an application for Vault in the tenant.
|
|
2. Add the redirect URIs of the type "Web":
|
|
|
|
- `http://localhost:8250/oidc/callback` for the CLI login.
|
|
- `https://vault.example.com:8200/ui/vault/auth/oidc/oidc/callback` for the Vault UI login.
|
|
|
|
3. Record the **Application (client) ID** and the **Directory (tenant) ID**.
|
|
4. Under **Certificates & secrets**, create a client secret and record its value.
|
|
5. Under **Token configuration**, select **Add groups claim**, select the group types to include (**Security groups**), and under **Customize token properties by type** select **Group ID** in the **ID** section. The `groups` claim of the ID token then contains the object IDs of the groups of the user.
|
|
6. Create a security group of the users who may request certificates, add the users to it, and record the **Object ID** of the group.
|
|
7. Under **API permissions**, add the Microsoft Graph delegated permission `User.Read` and grant the admin consent. Entra ID omits the groups from the token of users who are members of more than 200 groups, and Vault then reads them through Graph.
|
|
|
|
## 2. Configure Vault
|
|
|
|
Role: **Administrator**
|
|
|
|
Point the CLI at the server and authenticate with an administrator token:
|
|
|
|
```bash
|
|
export VAULT_ADDR=https://vault.example.com:8200
|
|
export VAULT_TOKEN=<admin-token>
|
|
```
|
|
|
|
1. Create a policy that allows signing certificate requests with the `wif-client` role and nothing else.
|
|
2. Enable the OIDC authentication method and configure Entra ID as the provider.
|
|
3. Create a role that accepts only members of the group and attaches the policy. The token is valid for 15 minutes and can be renewed up to 1 hour.
|
|
|
|
`pki-client.hcl`:
|
|
|
|
```hcl
|
|
path "pki_int/sign/wif-client" {
|
|
capabilities = ["update"]
|
|
}
|
|
```
|
|
|
|
`oidc-config.json`:
|
|
|
|
```json
|
|
{
|
|
"oidc_discovery_url": "https://login.microsoftonline.com/<tenant-id>/v2.0",
|
|
"oidc_client_id": "<client-id>",
|
|
"oidc_client_secret": "<client-secret>",
|
|
"default_role": "entra-pki",
|
|
"provider_config": {
|
|
"provider": "azure"
|
|
}
|
|
}
|
|
```
|
|
|
|
`oidc-role.json`:
|
|
|
|
```json
|
|
{
|
|
"role_type": "oidc",
|
|
"user_claim": "sub",
|
|
"allowed_redirect_uris": [
|
|
"http://localhost:8250/oidc/callback",
|
|
"https://vault.example.com:8200/ui/vault/auth/oidc/oidc/callback"
|
|
],
|
|
"groups_claim": "groups",
|
|
"oidc_scopes": ["profile"],
|
|
"bound_claims": {"groups": ["<group-object-id>"]},
|
|
"token_policies": ["pki-client"],
|
|
"token_ttl": "15m",
|
|
"token_max_ttl": "1h"
|
|
}
|
|
```
|
|
|
|
```bash
|
|
vault policy write pki-client pki-client.hcl
|
|
|
|
vault auth enable oidc
|
|
|
|
vault write auth/oidc/config @oidc-config.json
|
|
|
|
vault write auth/oidc/role/entra-pki @oidc-role.json
|
|
```
|
|
|
|
Delete `oidc-config.json` after the configuration is written, because it contains the client secret.
|
|
|
|
The redirect URIs in the role must match the ones registered in Entra ID exactly, including the scheme, the port and the trailing path. The `bound_claims` entry limits the login to the members of the group, the other Entra ID users get an error at login.
|
|
|
|
The CLI cannot express the `bound_claims` map as `key=value` arguments, which is why the configuration is read from JSON files.
|
|
|
|
> **Note:** The `wif-client` role accepts any common name (`allow_any_name=true`). Every member of the group can therefore request a certificate with any subject CN, including the CN that other users or workloads rely on as the WiF subject. Restrict the role (`allowed_domains`, `allow_bare_domains`, `enforce_hostnames`) when the users must not choose their CN freely.
|
|
|
|
## 3. Request a certificate
|
|
|
|
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`.
|
|
|
|
```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.
|
|
|
|
```bash
|
|
export VAULT_TOKEN=$(node vault_oidc_login.mjs --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.
|
|
|
|
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.
|
|
|
|
3. Generate the client key and the CSR locally. The key is never sent to Vault.
|
|
|
|
```bash
|
|
openssl req \
|
|
-newkey rsa:4096 \
|
|
-keyout client_key.pem \
|
|
-noenc \
|
|
-subj "/CN=test-client" \
|
|
-out client.csr
|
|
```
|
|
|
|
4. Sign the CSR with the Vault CA.
|
|
|
|
```bash
|
|
vault write -field=certificate pki_int/sign/wif-client \
|
|
csr=@client.csr \
|
|
common_name=test-client \
|
|
ttl=8760h > client_cert.pem
|
|
```
|
|
|
|
5. Check the certificate.
|
|
|
|
```bash
|
|
openssl x509 -in client_cert.pem -noout -subject -issuer -dates -ext extendedKeyUsage
|
|
```
|
|
|
|
6. Revoke the token when the work is done.
|
|
|
|
```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`.
|
|
|
|
## Next steps
|
|
|
|
1. Create the credentials configuration `cred.json` for `client_cert.pem` and `client_key.pem`. The names come from `env.sh`.
|
|
|
|
```bash
|
|
gcloud iam workload-identity-pools create-cred-config \
|
|
"projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/providers/${PROVIDER_ID}" \
|
|
--service-account="${SA_EMAIL}" \
|
|
--credential-cert-path=client_cert.pem \
|
|
--credential-cert-private-key-path=client_key.pem \
|
|
--output-file=cred.json
|
|
```
|
|
|
|
2. Activate the service account with `gcloud auth login --cred-file=cred.json`.
|
|
3. Run the WIF tests.
|
|
|
|
Example test commands are in `test_wif.sh`.
|