diff --git a/.gitignore b/.gitignore index fc066be..3f43855 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ vault-data/ vault-init.txt vault.hcl *.csr +editor.json diff --git a/README.md b/README.md index 043cdc7..6737166 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,8 @@ bash vault_gcp_update.sh For a new setup, continue with step 2 instead. +Requesting the client certificate from a remote Vault server with a user account is described in `docs/vault-userpass.md` (user and password) and `docs/vault-entra-id.md` (Microsoft Entra ID). + ## 2. Create the trust store `make_trust_store.mjs` creates `trust_store.yaml`, a YAML file that holds the root CA as the trust anchor and the issuing CA as an intermediate CA. It uses the `yaml` npm package, declared in `package.json`: diff --git a/docs/vault-entra-id.md b/docs/vault-entra-id.md new file mode 100644 index 0000000..801ee2a --- /dev/null +++ b/docs/vault-entra-id.md @@ -0,0 +1,143 @@ +# 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, 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**, add a groups claim and select the ID token. +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= +``` + +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"] +} +``` + +```bash +vault policy write pki-client pki-client.hcl + +vault auth enable oidc + +vault write auth/oidc/config - <<'EOF' +{ + "oidc_discovery_url": "https://login.microsoftonline.com//v2.0", + "oidc_client_id": "", + "oidc_client_secret": "", + "default_role": "entra-pki", + "provider_config": { + "provider": "azure" + } +} +EOF + +vault write auth/oidc/role/entra-pki - <<'EOF' +{ + "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": [""]}, + "token_policies": ["pki-client"], + "token_ttl": "15m", + "token_max_ttl": "1h" +} +EOF +``` + +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 commands above pass JSON on standard input, because the CLI cannot express the `bound_claims` map as `key=value` arguments. + +> **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` 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 + ``` + +2. Log in with the Entra ID account. The CLI starts a listener on `localhost:8250` and opens the browser with the Entra ID sign-in page. After the sign-in the CLI stores the token in the token helper file (`~/.vault-token`). + + ```bash + vault login -method=oidc role=entra-pki + ``` + +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 + ``` + +The user token can only sign with the `wif-client` role. Reading the role or any other path returns `permission denied`. diff --git a/docs/vault-userpass.md b/docs/vault-userpass.md new file mode 100644 index 0000000..6e2d453 --- /dev/null +++ b/docs/vault-userpass.md @@ -0,0 +1,109 @@ +# Requesting a client certificate from a remote Vault server with user and password authentication + +This guide shows how an administrator configures the Vault userpass authentication method, and how a user logs in 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`. +- `vault` and `openssl` on the workstation of the administrator and of the user. + +The commands are written for bash. + +## 1. Configure the authentication + +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= +``` + +1. Create a policy that allows signing certificate requests with the `wif-client` role and nothing else. +2. Enable the userpass authentication method. +3. Create the user and attach the policy. The token of the user 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"] +} +``` + +```bash +vault policy write pki-client pki-client.hcl + +vault auth enable userpass + +read -r -s -p "Password: " PASSWORD; echo +printf '%s' "${PASSWORD}" | vault write auth/userpass/users/alice \ + password=- \ + token_policies=pki-client \ + token_ttl=15m \ + token_max_ttl=1h +unset PASSWORD +``` + +`password=-` makes the CLI read the password from standard input, so it does not appear in the process list or in the shell history. + +> **Note:** The `wif-client` role accepts any common name (`allow_any_name=true`). Every user with the `pki-client` policy 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. + +## 2. Request a certificate + +Role: **User** + +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 + ``` + +2. Log in. The CLI asks for the password and stores the token in the token helper file (`~/.vault-token`). + + ```bash + vault login -method=userpass username=alice + ``` + +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 + ``` + +The user token can only sign with the `wif-client` role. Reading the role or any other path returns `permission denied`. + +To avoid storing the token on disk, log in with `-no-store -token-only` and keep the token in the environment: + +```bash +export VAULT_TOKEN=$(vault login -no-store -token-only -method=userpass username=alice) +```