Beginner-Friendly Hands-On Lab for Ubuntu Linux
1. Lab Objective
In this lab, we will build the following environment from scratch:
Ubuntu Linux Server
|
+------------+-------------+
| |
HashiCorp Vault HashiCorp Consul
127.0.0.1:8200 127.0.0.1:8500
| |
| |
userpass auth |
| |
student1 |
| |
consul-engine-admin |
Vault Policy |
| |
v |
Consul Secrets Engine ------------+
|
|
student-role
|
v
Dynamic Consul Token
|
v
Consul ACL Policy
student-kv
|
v
student/* KV
By the end of the lab, students will understand:
- how to install Vault and Consul;
- how to start Vault in development mode;
- how to start Consul in development mode with ACLs;
- how Consul ACL bootstrap works;
- how to enable the Vault Consul secrets engine;
- how Vault authenticates to Consul;
- how to create a Consul ACL policy;
- how to create a Vault ACL policy;
- how to enable the Vault
userpassauthentication method; - how to create a Vault user;
- how to attach a Vault policy to that user;
- how to log in using username/password;
- how to perform Create, Read, Update, List, and Delete operations on Consul secrets-engine roles;
- how to generate short-lived Consul credentials;
- how to use the dynamically generated Consul token;
- how Vault leases work;
- how to renew a lease;
- how to revoke a lease;
- how to confirm that the Consul credential stops working after revocation;
- how Vault policies prevent access to unrelated secret engines.
The current Vault documentation is on the Vault 2.x release line. As of August 9, 2026, HashiCorp’s release notes list Vault 2.0.4, released August 4, 2026. The current Consul install material lists Consul 2.0.2.
2. Important Lab Warning
This tutorial intentionally uses:
Vault development mode
Consul development mode
HTTP instead of HTTPS
A known Vault root token
A locally stored Consul bootstrap token
A broad Vault policy
These choices make the environment easy for beginners to understand.
Do not use this architecture in production.
Vault development mode:
- starts initialized;
- starts unsealed;
- stores its data in memory;
- listens on
127.0.0.1:8200; - can use a predetermined root token;
- loses its data when stopped.
HashiCorp explicitly identifies dev mode as an experimentation/development environment rather than a production deployment.
3. Lab Environment
Recommended environment:
Operating System : Ubuntu Server
Vault : 2.0.4 or newer compatible 2.x
Consul : 2.0.2 or newer compatible 2.x
Vault Address : http://127.0.0.1:8200
Consul Address : http://127.0.0.1:8500
Vault Root Token : root
Vault User : student1
Vault Password : StudentLab@2026
Vault Policy : consul-engine-admin
Vault Engine : consul/
Consul Policy : student-kv
Vault Consul Role : student-role
Consul KV Prefix : student/
Everything in this tutorial runs on the same Ubuntu server.
4. Understanding the Two Different Policies
Before running commands, students should understand that this lab uses two completely different policy systems.
Vault Policy
Our Vault policy will be named:
consul-engine-admin
It controls what the Vault user student1 can do inside Vault.
For example:
Can student1 create a Vault Consul role?
Can student1 read the role?
Can student1 modify the role?
Can student1 delete the role?
Can student1 request a dynamic Consul token?
Vault policies map paths to capabilities such as:
create
read
update
delete
list
Vault’s current policy documentation maps these capabilities to operations on Vault paths.
Consul ACL Policy
Our Consul ACL policy will be named:
student-kv
This policy controls what a Consul token generated by Vault can do inside Consul.
We will allow the generated token to manage:
student/*
but not:
private/*
production/*
anything-else/*
A Consul write policy permits reading and modifying the matching resources.
5. Step 1 — Update Ubuntu
Run:
sudo apt update
sudo apt upgrade -y
Install utilities required during the lab:
sudo apt install -y \
curl \
wget \
gpg \
lsb-release \
jq
Verify jq:
jq --version
Example:
jq-1.7
6. Step 2 — Add the Official HashiCorp Repository
Download the HashiCorp repository signing key:
wget -O- https://apt.releases.hashicorp.com/gpg \
| sudo gpg --dearmor \
-o /usr/share/keyrings/hashicorp-archive-keyring.gpg
Add the HashiCorp repository:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/hashicorp.list
Update the package list:
sudo apt update
HashiCorp publishes both Vault and Consul through its Ubuntu/Debian package repository.
7. Step 3 — Install Vault and Consul
Install both packages:
sudo apt install -y vault consul
8. Step 4 — Check Installed Versions
Run:
vault version
At the time this lab was validated against the release documentation, the current Vault release is:
Vault v2.0.4
Now check Consul:
consul version
Current Consul should be from the 2.0.x line, with the current install release being:
Consul v2.0.2
Consul’s version command is the supported way to inspect the installed version.
9. Step 5 — Create a Working Directory
Rather than scattering files around the server, create one directory for everything related to the lab.
Run:
mkdir -p "$HOME/vault-consul-lab"
Set an environment variable:
export LAB_DIR="$HOME/vault-consul-lab"
Verify:
echo "$LAB_DIR"
Expected:
/home/<your-user>/vault-consul-lab
Move into the directory:
cd "$LAB_DIR"
10. Step 6 — Configure Consul ACLs
Create:
cat > "$LAB_DIR/consul-lab.hcl" <<'EOF'
datacenter = "dc1"
acl = {
enabled = true
default_policy = "deny"
enable_token_persistence = true
}
EOF
Display the file:
cat "$LAB_DIR/consul-lab.hcl"
Expected:
datacenter = "dc1"
acl = {
enabled = true
default_policy = "deny"
enable_token_persistence = true
}
This enables the Consul ACL system and sets the default policy to deny. HashiCorp recommends deny for a new secured Consul environment.
11. Step 7 — Start Consul
First ensure another Consul process is not already running:
pgrep -a consul || true
For a fresh lab, there should normally be no output.
Start Consul:
nohup consul agent \
-dev \
-node=consul-lab \
-config-file="$LAB_DIR/consul-lab.hcl" \
> "$LAB_DIR/consul.log" 2>&1 &
Save the process ID:
echo $! > "$LAB_DIR/consul.pid"
Display the PID:
cat "$LAB_DIR/consul.pid"
Wait approximately two seconds:
sleep 2
Development mode runs an in-memory single-server Consul agent and is intended for rapid development and experimentation.
12. Step 8 — Verify Consul
Check the log:
tail -20 "$LAB_DIR/consul.log"
Check whether port 8500 is listening:
ss -lnt | grep 8500
You should see something similar to:
LISTEN ... 127.0.0.1:8500
Check the Consul leader:
curl -s http://127.0.0.1:8500/v1/status/leader
Expected output will resemble:
"127.0.0.1:8300"
The exact address may vary slightly.
13. Step 9 — Bootstrap the Consul ACL System
Consul ACLs are enabled, but no management token exists yet.
Run:
consul acl bootstrap -format=json \
> "$LAB_DIR/consul-bootstrap.json"
Protect the file:
chmod 600 "$LAB_DIR/consul-bootstrap.json"
View the non-secret portions:
jq 'del(.SecretID)' "$LAB_DIR/consul-bootstrap.json"
The bootstrap command creates the initial token associated with Consul’s unrestricted global-management policy. Consul only permits the ACL bootstrap operation once for a given ACL state.
14. Step 10 — Capture the Consul Management Token
Extract the Secret ID:
export CONSUL_MGMT_TOKEN="$(
jq -r '.SecretID' "$LAB_DIR/consul-bootstrap.json"
)"
Check that we captured something:
test -n "$CONSUL_MGMT_TOKEN" \
&& test "$CONSUL_MGMT_TOKEN" != "null" \
&& echo "Consul management token captured successfully."
Expected:
Consul management token captured successfully.
Do not print this token unnecessarily.
Configure the Consul CLI to use it:
export CONSUL_HTTP_TOKEN="$CONSUL_MGMT_TOKEN"
15. Step 11 — Test Consul Administrative Access
Run:
consul acl policy list
You should see built-in policies such as:
global-management
builtin/global-read-only
The exact displayed formatting may differ.
16. Step 12 — Create a Consul Policy for Vault-Generated Tokens
We do not want Vault to generate unrestricted Consul management tokens for students.
Instead, create a restricted Consul ACL policy.
Run:
cat > "$LAB_DIR/student-kv-policy.hcl" <<'EOF'
key_prefix "student/" {
policy = "write"
}
EOF
Display it:
cat "$LAB_DIR/student-kv-policy.hcl"
Expected:
key_prefix "student/" {
policy = "write"
}
This policy allows generated Consul tokens to read and modify keys underneath:
student/
Consul’s current ACL rules define write as permitting both read and modification operations.
17. Step 13 — Create the Consul ACL Policy
Run:
consul acl policy create \
-name="student-kv" \
-description="Vault generated tokens may manage student KV data" \
-rules=@"$LAB_DIR/student-kv-policy.hcl"
A successful response will contain fields similar to:
ID:
Name: student-kv
Description: Vault generated tokens may manage student KV data
Rules:
key_prefix "student/" {
policy = "write"
}
Consul’s current CLI supports loading ACL policy rules from a file by prefixing the filename with @.
18. Step 14 — Read the Consul Policy
Run:
consul acl policy read -name="student-kv"
Verify that you see:
Name: student-kv
and:
key_prefix "student/" {
policy = "write"
}
At this point the Consul side of the permission model exists.
19. Step 15 — Start Vault
Before starting Vault, check whether another Vault process exists:
pgrep -a vault || true
Start the Vault development server:
nohup vault server \
-dev \
-dev-root-token-id="root" \
-dev-listen-address="127.0.0.1:8200" \
> "$LAB_DIR/vault.log" 2>&1 &
Save the PID:
echo $! > "$LAB_DIR/vault.pid"
Wait:
sleep 2
HashiCorp’s current Vault server CLI supports -dev, -dev-root-token-id, and -dev-listen-address.
20. Step 16 — Configure the Vault CLI
Set the Vault server address:
export VAULT_ADDR='http://127.0.0.1:8200'
Set the root token:
export VAULT_TOKEN='root'
Confirm:
echo "$VAULT_ADDR"
Expected:
http://127.0.0.1:8200
21. Step 17 — Verify Vault
Run:
vault status
Expected output should contain:
Initialized true
Sealed false
Because this is a development server, Vault starts initialized and unsealed automatically.
22. Step 18 — Confirm Root Access
Run:
vault token lookup
You should see:
policies [root]
The root policy gives unrestricted access and should only be used for the administrative setup section of this lab. Root tokens have unrestricted Vault privileges.
23. Step 19 — Check Existing Secrets Engines
Run:
vault secrets list
You will see several built-in mounts.
The important point is that:
consul/
should not exist yet.
24. Step 20 — Enable the Consul Secrets Engine
Run:
vault secrets enable consul
Expected:
Success! Enabled the consul secrets engine at: consul/
Verify:
vault secrets list
You should now find:
consul/
The supported command for enabling the secrets engine at its default mount is:
vault secrets enable consul
25. Step 21 — Configure Vault to Communicate with Consul
Vault now has the plugin, but the plugin does not yet know:
Where is Consul?
What credential should Vault use to create Consul tokens?
Configure it:
vault write consul/config/access \
address="127.0.0.1:8500" \
scheme="http" \
token="$CONSUL_MGMT_TOKEN"
Expected:
Success! Data written to: consul/config/access
Vault’s Consul secrets engine requires the Consul address and, for an already-bootstrapped Consul ACL environment, a Consul management token that Vault can use to create and revoke ACL tokens.
26. Step 22 — Stop Using the Consul Management Token Directly
This is an important lab practice.
Vault now has the credential it needs.
Remove the management credential from the active shell:
unset CONSUL_HTTP_TOKEN
unset CONSUL_MGMT_TOKEN
Verify:
env | grep CONSUL_HTTP_TOKEN || true
There should be no output.
From this point onward, we want students to work with credentials generated by Vault, not with the powerful Consul bootstrap credential.
27. Step 23 — Create the Vault Policy
Now create the policy that will eventually be attached to our Vault userpass user.
Run:
cat > "$LAB_DIR/consul-engine-admin.hcl" <<'EOF'
# Full CRUD-style access to everything underneath the
# Consul secrets engine mount.
#
# LAB ONLY: This includes sensitive Consul engine configuration.
path "consul/*" {
capabilities = [
"create",
"read",
"update",
"delete",
"list"
]
}
# Allow looking up an individual dynamic-secret lease.
path "sys/leases/lookup" {
capabilities = ["update"]
}
# Allow renewal of an individual lease.
path "sys/leases/renew" {
capabilities = ["update"]
}
path "sys/leases/renew/*" {
capabilities = ["update"]
}
# Allow revocation of an individual lease.
path "sys/leases/revoke" {
capabilities = ["update"]
}
path "sys/leases/revoke/*" {
capabilities = ["update"]
}
EOF
28. Step 24 — Examine the Vault Policy
Run:
cat "$LAB_DIR/consul-engine-admin.hcl"
The central section is:
path "consul/*" {
capabilities = [
"create",
"read",
"update",
"delete",
"list"
]
}
This means:
CREATE -> permitted
READ -> permitted
UPDATE -> permitted
DELETE -> permitted
LIST -> permitted
Vault policies are path based, and these capabilities map to the corresponding operations supported by a Vault endpoint.
29. Step 25 — Load the Policy into Vault
Run:
vault policy write \
consul-engine-admin \
"$LAB_DIR/consul-engine-admin.hcl"
Expected:
Success! Uploaded policy: consul-engine-admin
Vault supports loading an HCL ACL policy using vault policy write.
30. Step 26 — Read the Vault Policy
Run:
vault policy read consul-engine-admin
Verify the policy is displayed correctly.
31. Step 27 — List Vault Policies
Run:
vault policy list
You should see something similar to:
consul-engine-admin
default
root
32. Step 28 — Enable Userpass Authentication
Now enable username/password authentication.
Run:
vault auth enable userpass
Expected:
Success! Enabled userpass auth method at: userpass/
Verify:
vault auth list
You should find:
userpass/
Vault’s current documentation supports enabling the method with exactly:
vault auth enable userpass
33. Step 29 — Create One Vault User
Create:
Username: student1
Password: StudentLab@2026
Policy: consul-engine-admin
Run:
vault write auth/userpass/users/student1 \
password='StudentLab@2026' \
policies='consul-engine-admin' \
token_ttl='1h' \
token_max_ttl='4h'
Expected:
Success! Data written to: auth/userpass/users/student1
Vault’s userpass configuration associates users with policies, and successful authentication produces a Vault token carrying those policies.
34. Step 30 — Read the User Configuration
Still using the root token, run:
vault read auth/userpass/users/student1
You should see information including:
token_policies
token_ttl
token_max_ttl
The password itself will not be returned.
The current Userpass API exposes reading user configuration but does not reveal the stored password.
35. Step 31 — Administrator Setup Is Complete
At this point root has completed:
Consul installation
Consul ACL setup
Consul ACL policy creation
Vault installation
Vault Consul secrets engine
Vault-to-Consul connection
Vault policy
Userpass authentication
Vault user creation
Now we switch identities.
Everything from the next step should be performed as:
student1
36. Step 32 — Remove Root from the Current Shell
This step is extremely important.
Run:
unset VAULT_TOKEN
Verify:
echo "${VAULT_TOKEN:-VAULT_TOKEN is not set}"
Expected:
VAULT_TOKEN is not set
If students accidentally leave:
VAULT_TOKEN=root
then their tests are meaningless because every command continues to execute as root.
37. Step 33 — Login Using Userpass
Run:
vault login -method=userpass \
username='student1' \
password='StudentLab@2026'
Expected output will contain something similar to:
token <generated-vault-token>
token_duration 1h
token_renewable true
token_policies ["consul-engine-admin" "default"]
The precise token will be different.
The supported CLI authentication form is:
vault login -method=userpass username=... password=...
38. Step 34 — Verify the Current Vault Identity
Run:
vault token lookup
Check:
display_name
policies
ttl
The policies should include:
consul-engine-admin
default
They should not include:
root
This is a critical checkpoint.
39. Step 35 — Verify the Student’s Vault Capabilities
Run:
vault token capabilities consul/roles/student-role
The output should contain capabilities similar to:
create, delete, list, read, update
Now check the credential endpoint:
vault token capabilities consul/creds/student-role
Again, the policy allows access.
Now test an unrelated Vault engine:
vault token capabilities secret/data/my-secret
Expected:
deny
This proves that the student is not a Vault administrator.
They have permissions on:
consul/*
but not arbitrary Vault paths.
40. Step 36 — First Negative Security Test
Try writing into Vault’s development KV engine:
vault kv put secret/not-allowed \
username=student \
password=test
This should fail with a permission error similar to:
permission denied
This failure is correct.
The student’s policy only gives access to:
consul/*
This demonstrates authorization working correctly.
41. Step 37 — Understand Vault Consul Roles
The Vault Consul secrets engine uses roles.
Think of a Vault role as a template that tells Vault:
When someone requests Consul credentials from this role, which Consul policy should be attached to the generated token and how long should the token live?
Our role will be:
student-role
and it will reference the Consul policy:
student-kv
Current Vault Consul API documentation supports creating/updating roles with consul_policies, assigning TTLs, reading roles, listing them, deleting them, and generating credentials from them.
42. Step 38 — CREATE a Consul Secrets-Engine Role
Run as student1:
vault write consul/roles/student-role \
consul_policies='student-kv' \
ttl='30m' \
max_ttl='1h'
Expected:
Success! Data written to: consul/roles/student-role
We have just performed the CREATE operation.
43. Step 39 — READ the Role
Run:
vault read consul/roles/student-role
You should see fields corresponding to the role, including its Consul policy and TTL configuration.
This is the READ operation.
44. Step 40 — LIST the Roles
Run:
vault list consul/roles
Expected:
Keys
----
student-role
This is the LIST operation.
The Consul secrets engine explicitly supports LIST /consul/roles.
45. Step 41 — UPDATE the Role
Originally:
TTL = 30 minutes
Max TTL = 1 hour
Change it to:
TTL = 15 minutes
Max TTL = 30 minutes
Run:
vault write consul/roles/student-role \
consul_policies='student-kv' \
ttl='15m' \
max_ttl='30m'
Expected:
Success! Data written to: consul/roles/student-role
This demonstrates UPDATE.
46. Step 42 — Verify the Update
Run:
vault read consul/roles/student-role
Verify the new TTL settings.
47. Step 43 — DELETE the Role
Run:
vault delete consul/roles/student-role
Expected:
Success! Data deleted (if it existed) at: consul/roles/student-role
This demonstrates DELETE.
The secrets engine’s current API supports DELETE /consul/roles/:name.
48. Step 44 — Confirm the Role Is Gone
Run:
vault list consul/roles
You may receive:
No value found at consul/roles
Alternatively, if other roles exist, student-role should be absent.
49. Step 45 — Recreate the Role
We need the role for the dynamic-credential portion of the lab.
Run:
vault write consul/roles/student-role \
consul_policies='student-kv' \
ttl='30m' \
max_ttl='1h'
Verify:
vault read consul/roles/student-role
50. CRUD Checkpoint
Students have now successfully demonstrated:
CREATE
vault write consul/roles/student-role ...
READ
vault read consul/roles/student-role
UPDATE
vault write consul/roles/student-role ...
LIST
vault list consul/roles
DELETE
vault delete consul/roles/student-role
This is the core Vault ACL policy exercise.
51. Step 46 — Generate a Dynamic Consul Credential
Now request a Consul token.
Run:
vault read consul/creds/student-role
Vault should return information resembling:
Key Value
--- -----
lease_id consul/creds/student-role/<unique-id>
lease_duration 30m
lease_renewable true
token <dynamic-consul-token>
Exact fields and values may vary.
Every request creates a new Consul ACL token.
The current Consul secrets-engine credential endpoint is a read operation on:
consul/creds/:role
even though that read causes Vault to create a new credential. This is why the Vault policy requires read capability on that path.
52. Step 47 — Capture the Dynamic Credential Safely
Instead of manually copying the token, request JSON:
CREDS_JSON="$(
vault read -format=json consul/creds/student-role
)"
Capture the Vault lease ID:
export LEASE_ID="$(
printf '%s' "$CREDS_JSON" \
| jq -r '.lease_id'
)"
Capture the generated Consul token:
export DYNAMIC_CONSUL_TOKEN="$(
printf '%s' "$CREDS_JSON" \
| jq -r '.data.token'
)"
Remove the JSON variable:
unset CREDS_JSON
53. Step 48 — Verify Variables Were Captured
Check the lease ID:
echo "$LEASE_ID"
It should resemble:
consul/creds/student-role/<unique-id>
Do not print the Consul token unnecessarily.
Instead verify that it exists:
test -n "$DYNAMIC_CONSUL_TOKEN" \
&& test "$DYNAMIC_CONSUL_TOKEN" != "null" \
&& echo "Dynamic Consul token captured successfully."
Expected:
Dynamic Consul token captured successfully.
54. Step 49 — Use the Vault-Generated Consul Token
We now use the generated token directly against Consul.
Write a KV value:
CONSUL_HTTP_TOKEN="$DYNAMIC_CONSUL_TOKEN" \
consul kv put \
student/application/database \
"training-database"
Expected:
Success! Data written to: student/application/database
Depending on the Consul CLI version, successful KV writes may instead display:
Success! Data written
or:
true
The important result is success.
55. Step 50 — Read the Consul KV Value
Run:
CONSUL_HTTP_TOKEN="$DYNAMIC_CONSUL_TOKEN" \
consul kv get student/application/database
Expected:
training-database
The Vault-generated Consul token works.
56. Step 51 — Update the Consul KV Value
Run:
CONSUL_HTTP_TOKEN="$DYNAMIC_CONSUL_TOKEN" \
consul kv put \
student/application/database \
"production-style-training-value"
Read it:
CONSUL_HTTP_TOKEN="$DYNAMIC_CONSUL_TOKEN" \
consul kv get student/application/database
Expected:
production-style-training-value
57. Step 52 — List Student Keys
Run:
CONSUL_HTTP_TOKEN="$DYNAMIC_CONSUL_TOKEN" \
consul kv get -recurse student/
You should see the values underneath:
student/
A token with Consul write access on the matching prefix also has read/list functionality for that prefix.
58. Step 53 — Prove the Consul Token Is Restricted
Now try writing outside the permitted prefix:
CONSUL_HTTP_TOKEN="$DYNAMIC_CONSUL_TOKEN" \
consul kv put \
production/database/password \
"should-not-work"
This should fail with an ACL/permission error.
That failure is correct.
Why?
Because the dynamically generated token has:
key_prefix "student/" {
policy = "write"
}
It does not have:
key_prefix "production/" {
policy = "write"
}
This demonstrates the difference between:
Vault authorization
and:
Consul authorization
59. Step 54 — Create a Value for the Lease Test
Create another key:
CONSUL_HTTP_TOKEN="$DYNAMIC_CONSUL_TOKEN" \
consul kv put \
student/lease-test \
"credential-is-currently-valid"
Verify:
CONSUL_HTTP_TOKEN="$DYNAMIC_CONSUL_TOKEN" \
consul kv get student/lease-test
Expected:
credential-is-currently-valid
Keep this key in Consul.
We will use it to prove credential revocation.
60. Step 55 — Understand Vault Leases
Dynamic secrets issued by Vault have leases.
Our lease ID looks similar to:
consul/creds/student-role/ABC123...
The lease tells Vault:
which dynamic secret was issued
when it was issued
how long it should live
whether it can be renewed
how Vault should revoke it
Vault’s lease system tracks dynamic secrets and automatically revokes them when their lease expires.
61. Step 56 — Lookup the Lease
Run:
vault lease lookup "$LEASE_ID"
Look for fields such as:
issue_time
expire_time
last_renewal_time
ttl
62. Step 57 — Renew the Dynamic Credential
Run:
vault lease renew \
-increment=10m \
"$LEASE_ID"
Expected output resembles:
lease_id consul/creds/student-role/...
lease_duration ...
lease_renewable true
The requested increment is advisory; the backend may apply TTL limits configured by the role or mount.
63. Step 58 — Confirm the Token Still Works
Run:
CONSUL_HTTP_TOKEN="$DYNAMIC_CONSUL_TOKEN" \
consul kv get student/lease-test
Expected:
credential-is-currently-valid
The token is still active.
64. Step 59 — Revoke the Dynamic Credential
Now manually revoke its Vault lease.
Run:
vault lease revoke \
-sync \
"$LEASE_ID"
Expected:
Success! Revoked lease: consul/creds/student-role/...
Using -sync makes the revocation synchronous rather than merely queueing the work in the background. The current Vault CLI exposes this option on vault lease revoke.
65. Step 60 — Prove the Consul Credential Was Revoked
Try the same Consul command using the old token:
CONSUL_HTTP_TOKEN="$DYNAMIC_CONSUL_TOKEN" \
consul kv get student/lease-test
This should now fail with a Consul ACL/token error.
This failure is the expected result.
The sequence proves:
Vault generated the token
↓
Token worked in Consul
↓
Vault tracked it using a lease
↓
Vault revoked the lease
↓
Vault revoked the Consul token
↓
Old Consul token stopped working
Vault’s Consul secrets engine requires management-level Consul access precisely so that it can create and revoke these generated ACL tokens.
66. Step 61 — Request Another Dynamic Credential
Run:
vault read consul/creds/student-role
You will receive:
a different lease ID
a different Consul token
This demonstrates one of the major benefits of dynamic credentials:
Applications do not need to share one permanent Consul password/token.
New credentials can be requested when required and expired/revoked afterward.
67. Complete Authentication and Authorization Flow
Students should now understand this entire chain:
1. student1 supplies username/password
|
v
2. Vault userpass authenticates student1
|
v
3. Vault issues student1 a Vault token
|
v
4. Vault token contains consul-engine-admin policy
|
v
5. Policy permits operations against consul/*
|
v
6. student1 requests consul/creds/student-role
|
v
7. Vault reads student-role
|
v
8. student-role references Consul policy student-kv
|
v
9. Vault authenticates to Consul using its management token
|
v
10. Vault asks Consul to create a new ACL token
|
v
11. Consul attaches student-kv to that token
|
v
12. Vault returns the short-lived token to student1
|
v
13. Token can access student/*
|
v
14. Vault tracks the credential using a lease
|
v
15. Lease expiration/revocation causes the Consul token to be revoked
68. Vault CRUD Summary
CREATE
vault write consul/roles/student-role \
consul_policies='student-kv' \
ttl='30m' \
max_ttl='1h'
READ
vault read consul/roles/student-role
UPDATE
vault write consul/roles/student-role \
consul_policies='student-kv' \
ttl='15m' \
max_ttl='30m'
LIST
vault list consul/roles
DELETE
vault delete consul/roles/student-role
GENERATE DYNAMIC SECRET
vault read consul/creds/student-role
69. Userpass Command Summary
Enable Userpass
vault auth enable userpass
Create User
vault write auth/userpass/users/student1 \
password='StudentLab@2026' \
policies='consul-engine-admin' \
token_ttl='1h' \
token_max_ttl='4h'
Read User
Root/admin only in this lab:
vault read auth/userpass/users/student1
Login
vault login -method=userpass \
username='student1' \
password='StudentLab@2026'
The current userpass API also supports reading users, updating users/passwords/policies, listing users, and deleting them.
70. Consul Secrets Engine Command Summary
Enable Engine
vault secrets enable consul
Configure Connection
vault write consul/config/access \
address="127.0.0.1:8500" \
scheme="http" \
token="<CONSUL-MANAGEMENT-TOKEN>"
Create Role
vault write consul/roles/student-role \
consul_policies='student-kv' \
ttl='30m' \
max_ttl='1h'
Read Role
vault read consul/roles/student-role
List Roles
vault list consul/roles
Delete Role
vault delete consul/roles/student-role
Generate Credential
vault read consul/creds/student-role
These paths match the current Vault Consul secrets-engine API: configuration at consul/config/access, role management at consul/roles/:name, and credential generation at consul/creds/:name.
71. Vault Policy Used in This Lab
Complete file:
path "consul/*" {
capabilities = [
"create",
"read",
"update",
"delete",
"list"
]
}
path "sys/leases/lookup" {
capabilities = ["update"]
}
path "sys/leases/renew" {
capabilities = ["update"]
}
path "sys/leases/renew/*" {
capabilities = ["update"]
}
path "sys/leases/revoke" {
capabilities = ["update"]
}
path "sys/leases/revoke/*" {
capabilities = ["update"]
}
72. Why This Vault Policy Is Intentionally Broad
The requirement for this particular lab is:
Give one user complete CRUD-style access to the Consul secrets engine.
Therefore:
path "consul/*"
is intentionally broad.
It also means the user could potentially change sensitive configuration underneath the Consul engine.
That makes this policy appropriate for:
training
labs
experimentation
administrative demonstrations
but usually not appropriate for a normal application in production.
73. Safer Production-Style Vault Policy
After students understand the broad policy, show them this safer version:
# Allow role management.
path "consul/roles" {
capabilities = ["list"]
}
path "consul/roles/*" {
capabilities = [
"create",
"read",
"update",
"delete"
]
}
# Allow credential generation.
path "consul/creds/*" {
capabilities = ["read"]
}
# Do NOT give the user access to:
#
# consul/config/access
This illustrates an important Vault security principle:
Give the identity only the permissions it actually requires.
74. Student Verification Checklist
A student has successfully completed the lab when all of these conditions are true:
[ ] vault version works
[ ] consul version works
[ ] Consul listens on 127.0.0.1:8500
[ ] Consul ACL system has been bootstrapped
[ ] Consul policy student-kv exists
[ ] Vault listens on 127.0.0.1:8200
[ ] Vault is initialized
[ ] Vault is unsealed
[ ] consul/ secrets engine exists
[ ] Vault can communicate with Consul
[ ] consul-engine-admin Vault policy exists
[ ] userpass auth method exists
[ ] student1 exists
[ ] student1 can login
[ ] student1 receives consul-engine-admin
[ ] student1 does not have root policy
[ ] student1 cannot write to secret/
[ ] student1 can create student-role
[ ] student1 can read student-role
[ ] student1 can list roles
[ ] student1 can update student-role
[ ] student1 can delete student-role
[ ] student1 can generate a Consul token
[ ] generated token can write student/*
[ ] generated token can read student/*
[ ] generated token cannot write production/*
[ ] generated credential has a Vault lease
[ ] lease can be renewed
[ ] lease can be revoked
[ ] revoked Consul token stops working
75. Troubleshooting — Vault Connection Refused
Error:
connect: connection refused
Check:
pgrep -a vault
Check port:
ss -lnt | grep 8200
Check log:
tail -50 "$LAB_DIR/vault.log"
Check:
echo "$VAULT_ADDR"
It should be:
http://127.0.0.1:8200
76. Troubleshooting — Consul Connection Refused
Check process:
pgrep -a consul
Check port:
ss -lnt | grep 8500
Check log:
tail -50 "$LAB_DIR/consul.log"
Check leader:
curl -s http://127.0.0.1:8500/v1/status/leader
77. Troubleshooting — Consul ACL Bootstrap Fails
If:
consul acl bootstrap
reports that ACL bootstrapping has already occurred, you probably still have an existing Consul development server/state.
For this disposable lab, stop Consul:
kill "$(cat "$LAB_DIR/consul.pid")"
Confirm:
pgrep -a consul || true
Then restart the clean Consul development lab.
A Consul ACL environment can normally be bootstrapped only once.
78. Troubleshooting — Vault Permission Denied
Check which token you are using:
vault token lookup
Check policies:
vault token lookup -format=json \
| jq '.data.policies'
Expected for the student:
[
"consul-engine-admin",
"default"
]
Check capability:
vault token capabilities consul/roles/student-role
79. Troubleshooting — Student Accidentally Still Has Root Access
Check:
echo "${VAULT_TOKEN:-not-set}"
If you see:
root
then:
unset VAULT_TOKEN
Login again:
vault login -method=userpass \
username='student1' \
password='StudentLab@2026'
Then:
vault token lookup
Make sure:
root
is absent from the policies.
80. Troubleshooting — Dynamic Consul Credential Cannot Be Generated
Check that the role exists:
vault read consul/roles/student-role
Check the Consul policy name:
student-kv
Check Vault’s logs:
tail -50 "$LAB_DIR/vault.log"
Check Consul logs:
tail -50 "$LAB_DIR/consul.log"
Typical causes are:
Vault cannot reach Consul
Vault has an invalid Consul management token
student-kv Consul policy does not exist
student-role is incorrectly configured
81. Troubleshooting — Generated Token Cannot Access student/
Make sure you are actually supplying the generated token:
test -n "$DYNAMIC_CONSUL_TOKEN" \
&& echo "Token variable exists."
Then:
CONSUL_HTTP_TOKEN="$DYNAMIC_CONSUL_TOKEN" \
consul kv put student/test "hello"
If that fails, verify that the Consul role was created with:
consul_policies=student-kv
82. Troubleshooting — Wrong Password Entered Several Times
Vault includes user-lockout behavior for supported authentication methods including userpass.
Repeated failed authentication attempts can temporarily result in immediate permission-denied responses.
For a classroom environment, encourage students to copy the lab password correctly:
StudentLab@2026
rather than repeatedly guessing it.
83. Cleanup
Because both servers are operating in development mode, cleanup is simple.
Remove sensitive shell variables:
unset DYNAMIC_CONSUL_TOKEN
unset LEASE_ID
unset CONSUL_HTTP_TOKEN
unset CONSUL_MGMT_TOKEN
unset VAULT_TOKEN
Stop Vault:
kill "$(cat "$LAB_DIR/vault.pid")" 2>/dev/null || true
Stop Consul:
kill "$(cat "$LAB_DIR/consul.pid")" 2>/dev/null || true
Verify:
pgrep -a vault || true
and:
pgrep -a consul || true
Remove the lab files:
rm -rf "$LAB_DIR"
Optionally remove the locally cached Vault CLI token created by vault login:
rm -f "$HOME/.vault-token"
Unset:
unset LAB_DIR
unset VAULT_ADDR
Because development mode uses in-memory state, stopping the servers destroys the lab’s Vault and Consul runtime data.
84. What Students Should Remember
Authentication
Who are you?
In this lab:
userpass
username = student1
password = StudentLab@2026
Vault Authorization
What are you allowed to do in Vault?
Controlled by:
consul-engine-admin
Vault Consul Role
What type of Consul credential should Vault generate?
Controlled by:
student-role
Consul Authorization
What can the generated token do inside Consul?
Controlled by:
student-kv
Dynamic Secret
What credential did Vault dynamically create?
Answer:
A short-lived Consul ACL token.
Lease
How does Vault track the lifecycle of that credential?
Answer:
Using a Vault lease.
85. Final Architecture
+----------------------+
| student1 |
+----------+-----------+
|
| username/password
v
+----------------------+
| Vault Userpass |
+----------+-----------+
|
| authenticates
v
+----------------------+
| Vault Token |
| |
| consul-engine-admin |
+----------+-----------+
|
| permits access
v
+----------------------+
| Consul Secret |
| Engine: consul/ |
+----------+-----------+
|
| uses
v
+----------------------+
| student-role |
| |
| Consul policy: |
| student-kv |
+----------+-----------+
|
| Vault talks to Consul
v
+----------------------+
| Consul |
| ACL System |
+----------+-----------+
|
| dynamically creates
v
+----------------------+
| Dynamic Consul Token |
+----------+-----------+
|
| student-kv
v
+----------------------+
| Consul KV |
| |
| student/* -> ALLOW |
| other/* -> DENY |
+----------------------+
86. Master Lab Success
If the entire lab works correctly, the following sequence has been proven:
Ubuntu
↓
Vault
↓
userpass
↓
student1
↓
Vault Policy
↓
Consul Secrets Engine
↓
Vault Consul Role
↓
Dynamic Consul ACL Token
↓
Restricted Consul Permissions
↓
Vault Lease
↓
Renew
↓
Revoke
↓
Credential becomes invalid
That gives students an end-to-end introduction to Vault authentication, Vault authorization, the Consul dynamic secrets engine, Consul ACL authorization, dynamic credentials, TTLs, leases, renewal, and revocation in one coherent exercise.
Validation Notes
This guide has been syntax-checked against the current official Vault 2.x and Consul 2.0.x documentation, including the current Userpass API, Consul secrets-engine API, Vault ACL capability model, Consul ACL bootstrap and policy commands, and Vault lease renew/revoke commands. Vault’s current release notes identify 2.0.4 as the August 4, 2026 fix release.
The lab intentionally uses only currently documented fields such as consul_policies; older examples using legacy base64 policy, older Consul token types, or deprecated role fields have been avoided. Vault’s current Consul API specifically identifies older policy and policies role parameters as deprecated in favor of the current Consul-policy model.
The commands have been validated against the current documentation rather than executed on your particular Ubuntu host, so the first run on your server should also be treated as the final environment-specific runtime validation.