{"id":1044,"date":"2026-08-09T02:49:44","date_gmt":"2026-08-09T02:49:44","guid":{"rendered":"https:\/\/www.devopsschool.com\/tutorials\/?p=1044"},"modified":"2026-08-09T02:49:45","modified_gmt":"2026-08-09T02:49:45","slug":"hashicorp-vault-master-lab-postgresql-database-secrets-engine-userpass-authentication-vault-policy-dynamic-postgresql-credentials","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/tutorials\/hashicorp-vault-master-lab-postgresql-database-secrets-engine-userpass-authentication-vault-policy-dynamic-postgresql-credentials\/","title":{"rendered":"HashiCorp Vault Master Lab &#8211; PostgreSQL Database Secrets Engine + Userpass Authentication + Vault Policy + Dynamic PostgreSQL Credentials"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Complete Beginner-Friendly Hands-On Tutorial for Ubuntu Linux<\/h2>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">1. Lab Objective<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">In this lab we will build the following environment on a single Ubuntu Linux server:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>                         Ubuntu Server\n                              |\n              +---------------+---------------+\n              |                               |\n              v                               v\n      HashiCorp Vault                    PostgreSQL\n       127.0.0.1:8200                  127.0.0.1:5432\n              |                               |\n              |                               |\n         userpass auth                        |\n              |                               |\n          student1                            |\n              |                               |\n     postgresql-engine-admin                  |\n         Vault Policy                         |\n              |                               |\n              v                               |\n       database\/ Secrets Engine --------------+\n              |\n              v\n        PostgreSQL Plugin\n   postgresql-database-plugin\n              |\n              v\n          app-crud\n        Vault DB Role\n              |\n              v\n    Dynamic PostgreSQL Login\n              |\n              v\n    PostgreSQL Role: app_crud\n              |\n              v\n       public.app_data\n              |\n      SELECT \/ INSERT \/\n       UPDATE \/ DELETE\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">By the end of this lab, students will understand:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>how Vault&#8217;s database secrets engine works;<\/li>\n\n\n\n<li>how PostgreSQL integration works;<\/li>\n\n\n\n<li>how to install PostgreSQL on Ubuntu;<\/li>\n\n\n\n<li>how to prepare PostgreSQL for Vault;<\/li>\n\n\n\n<li>how to create a dedicated database account for Vault;<\/li>\n\n\n\n<li>how to create a PostgreSQL group role;<\/li>\n\n\n\n<li>how to enable the Vault database secrets engine;<\/li>\n\n\n\n<li>how to configure the PostgreSQL database plugin;<\/li>\n\n\n\n<li>how to enable <code>userpass<\/code>;<\/li>\n\n\n\n<li>how to create one Vault user;<\/li>\n\n\n\n<li>how to create and attach a Vault policy;<\/li>\n\n\n\n<li>how Vault ACL capabilities work;<\/li>\n\n\n\n<li>how to Create, Read, Update, List, and Delete Vault database roles;<\/li>\n\n\n\n<li>how Vault dynamically creates PostgreSQL usernames and passwords;<\/li>\n\n\n\n<li>how generated credentials receive limited PostgreSQL privileges;<\/li>\n\n\n\n<li>how to perform PostgreSQL SELECT, INSERT, UPDATE, and DELETE operations;<\/li>\n\n\n\n<li>how Vault leases work;<\/li>\n\n\n\n<li>how to renew database credentials;<\/li>\n\n\n\n<li>how to revoke database credentials;<\/li>\n\n\n\n<li>how revocation removes the PostgreSQL login;<\/li>\n\n\n\n<li>how to test both successful and denied operations.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">2. Version Validation<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">This tutorial targets the current <strong>Vault 2.x<\/strong> command and API model.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As of August 9, 2026, HashiCorp&#8217;s official installation documentation identifies:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Vault 2.0.4\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">as the current downloadable Vault release, released on August 4, 2026.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vault&#8217;s current PostgreSQL database plugin remains:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>postgresql-database-plugin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and the documented workflow remains:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database\/config\/&lt;connection&gt;\ndatabase\/roles\/&lt;role&gt;\ndatabase\/creds\/&lt;role&gt;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This lab deliberately installs PostgreSQL from the Ubuntu repository instead of forcing one PostgreSQL major release. This makes the tutorial usable on multiple supported Ubuntu releases.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Always verify your installed versions before proceeding.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">3. Important Terminology<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A very important point for beginners:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vault does <strong>not<\/strong> have a separate secrets-engine type named:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>postgresql\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Instead, Vault has:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">as the secrets engine.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Inside that engine we configure the:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>postgresql-database-plugin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Therefore:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Vault Database Secrets Engine\n            +\nPostgreSQL Database Plugin\n            =\nDynamic PostgreSQL Credentials\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">PostgreSQL is officially supported by Vault&#8217;s database secrets engine for dynamic and static database credentials.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">4. Lab Components<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">We will use these names throughout the tutorial.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Ubuntu server\n    |\n    +-- Vault\n    |      Address: http:\/\/127.0.0.1:8200\n    |      Dev root token: root\n    |\n    +-- Vault secrets engine\n    |      database\/\n    |\n    +-- Vault database connection\n    |      vaultlab-postgres\n    |\n    +-- Vault dynamic DB role\n    |      app-crud\n    |\n    +-- Vault auth method\n    |      userpass\/\n    |\n    +-- Vault user\n    |      student1\n    |\n    +-- Vault policy\n    |      postgresql-engine-admin\n    |\n    +-- PostgreSQL\n           Address: 127.0.0.1:5432\n           Database: vaultlab\n\n           Vault management user:\n               vaultadmin\n\n           PostgreSQL permission role:\n               app_crud\n\n           Test table:\n               public.app_data\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Passwords used for this training lab:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PostgreSQL Vault account:\nUsername: vaultadmin\nPassword: VaultAdmin2026\n\nVault userpass account:\nUsername: student1\nPassword: StudentLab2026\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">These are intentionally simple, fixed <strong>lab-only credentials<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Do not use these passwords in production.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">5. Important Security Warning<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">This lab uses:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Vault development mode\nVault root token = root\nHTTP instead of HTTPS\nPostgreSQL on localhost\nFixed demonstration passwords\nBroad Vault database-engine permissions\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is appropriate for:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>training\nclassrooms\nstudent labs\nexperimentation\nlearning\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It is <strong>not a production Vault architecture<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A production Vault deployment should use persistent storage, TLS, proper initialization\/unseal strategy, restricted administrative policies, audit devices, secure database networking, and separate operator\/application identities.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">6. Understanding the Three Permission Layers<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Students should understand that there are three different security layers.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Layer 1 \u2014 Vault Authentication<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Question:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Who are you?\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Answer:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>userpass\nstudent1\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Layer 2 \u2014 Vault Authorization<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Question:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>What may student1 do inside Vault?\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Answer:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Controlled by:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>postgresql-engine-admin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Vault policy.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Layer 3 \u2014 PostgreSQL Authorization<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Question:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>What may the dynamically generated PostgreSQL user do?\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Answer:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Controlled by PostgreSQL role:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app_crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The dynamic login will receive membership in:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app_crud\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">7. Why We Use a PostgreSQL Group Role<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of giving database permissions individually to every generated username, we create:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app_crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">as a PostgreSQL <code>NOLOGIN<\/code> role.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It receives permissions such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SELECT\nINSERT\nUPDATE\nDELETE\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Vault-created PostgreSQL accounts simply become members of:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app_crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Conceptually:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>                         app_crud\n                       PostgreSQL Role\n                             |\n                +------------+------------+\n                |            |            |\n                v            v            v\n          Dynamic User 1 User 2      User 3\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">PostgreSQL role membership allows privileges assigned to one role to be inherited by member roles.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This makes database permission management much cleaner.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">8. Step 1 \u2014 Update Ubuntu<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Upgrade packages:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt upgrade -y\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Install basic utilities:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y \\\n  curl \\\n  wget \\\n  gpg \\\n  lsb-release \\\n  jq\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verify:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>jq --version\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">9. Step 2 \u2014 Install PostgreSQL<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Install PostgreSQL server and client packages:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y \\\n  postgresql \\\n  postgresql-client \\\n  postgresql-contrib\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">10. Step 3 \u2014 Verify PostgreSQL Version<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>psql --version\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>psql (PostgreSQL) 16.x\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Your PostgreSQL major\/minor version may differ depending on your Ubuntu release.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That is acceptable for this lab.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">11. Step 4 \u2014 Start PostgreSQL<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl enable --now postgresql\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl status postgresql --no-pager\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>active\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">12. Step 5 \u2014 Verify PostgreSQL Is Listening<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pg_isready\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/var\/run\/postgresql:5432 - accepting connections\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Also check port 5432:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ss -lnt | grep 5432\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">13. Step 6 \u2014 Connect as PostgreSQL Administrator<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Ubuntu normally creates a Linux account named:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>postgres\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and a PostgreSQL superuser named:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>postgres\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Test it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should enter:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>postgres=#\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Exit:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\\q\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">14. Step 7 \u2014 Create the Lab Working Directory<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir -p \"$HOME\/vault-postgresql-lab\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Set:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export LAB_DIR=\"$HOME\/vault-postgresql-lab\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verify:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>echo \"$LAB_DIR\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Move into it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd \"$LAB_DIR\"\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">15. Step 8 \u2014 Create the PostgreSQL Vault Administrator<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Vault should not connect to PostgreSQL using the actual:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>postgres\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">superuser.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">HashiCorp recommends using a database-specific account for Vault with only the permissions necessary to create, update, and delete managed database users.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Create:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vaultadmin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql &lt;&lt;'SQL'\nSET password_encryption = 'scram-sha-256';\n\nCREATE ROLE vaultadmin\nWITH\n    LOGIN\n    CREATEROLE\n    PASSWORD 'VaultAdmin2026';\nSQL\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SET\nCREATE ROLE\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">16. Why Does <code>vaultadmin<\/code> Need CREATEROLE?<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Vault must dynamically execute operations such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE ROLE ...\nALTER ROLE ...\nDROP ROLE ...\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The account therefore needs permission to manage the PostgreSQL roles created by Vault.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">PostgreSQL documents that a <code>CREATEROLE<\/code> account can manage roles for which it has the appropriate administrative relationship; roles created by such an account automatically receive that relationship for their creator.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">17. Step 9 \u2014 Create the Application Permission Role<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Create:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app_crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql &lt;&lt;'SQL'\nCREATE ROLE app_crud NOLOGIN;\nSQL\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE ROLE\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This role cannot directly log in.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Its purpose is only to hold database privileges.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">18. Step 10 \u2014 Allow Vault to Grant <code>app_crud<\/code><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Vault&#8217;s:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vaultadmin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">account must be allowed to grant membership in:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app_crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">to dynamically created accounts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql &lt;&lt;'SQL'\nGRANT app_crud TO vaultadmin WITH ADMIN OPTION;\nSQL\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>GRANT ROLE\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>WITH ADMIN OPTION<\/code> allows <code>vaultadmin<\/code> to grant and revoke membership in <code>app_crud<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">19. Step 11 \u2014 Create the Training Database<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Create:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vaultlab\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres createdb vaultlab\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verify:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql -lqt | cut -d '|' -f 1\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should find:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vaultlab\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">20. Step 12 \u2014 Create a Test Table<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Create:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public.app_data\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql -d vaultlab &lt;&lt;'SQL'\nCREATE TABLE public.app_data (\n    id BIGSERIAL PRIMARY KEY,\n    item_name VARCHAR(100) NOT NULL,\n    quantity INTEGER NOT NULL DEFAULT 0\n);\n\nINSERT INTO public.app_data\n    (item_name, quantity)\nVALUES\n    ('linux-server', 10),\n    ('vault-server', 5),\n    ('postgresql-server', 3);\nSQL\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE TABLE\nINSERT 0 3\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">21. Step 13 \u2014 Examine the Table<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -d vaultlab \\\n  -c \"SELECT * FROM public.app_data;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code> id |      item_name       | quantity\n----+----------------------+----------\n  1 | linux-server         |       10\n  2 | vault-server         |        5\n  3 | postgresql-server    |        3\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">22. Step 14 \u2014 Restrict Public Schema Creation<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">To make our later negative permission tests deterministic, remove general CREATE permission from the <code>public<\/code> schema.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -d vaultlab \\\n  -c \"REVOKE CREATE ON SCHEMA public FROM PUBLIC;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>REVOKE\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">23. Step 15 \u2014 Give <code>app_crud<\/code> Database Permissions<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql -d vaultlab &lt;&lt;'SQL'\nGRANT CONNECT\nON DATABASE vaultlab\nTO app_crud;\n\nGRANT USAGE\nON SCHEMA public\nTO app_crud;\n\nGRANT SELECT, INSERT, UPDATE, DELETE\nON TABLE public.app_data\nTO app_crud;\n\nGRANT USAGE, SELECT\nON SEQUENCE public.app_data_id_seq\nTO app_crud;\nSQL\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>GRANT\nGRANT\nGRANT\nGRANT\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The sequence permission is necessary because our <code>BIGSERIAL<\/code> ID uses:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app_data_id_seq\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">when rows are inserted.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">24. Step 16 \u2014 Allow <code>vaultadmin<\/code> to Connect<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -d vaultlab \\\n  -c \"GRANT CONNECT ON DATABASE vaultlab TO vaultadmin;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>GRANT\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">25. PostgreSQL Permission Architecture<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">We now have:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>postgres\n  |\n  +-- vaultadmin\n  |      LOGIN\n  |      CREATEROLE\n  |      ADMIN OPTION on app_crud\n  |\n  +-- app_crud\n         NOLOGIN\n\n         CONNECT vaultlab\n         USAGE public schema\n         SELECT app_data\n         INSERT app_data\n         UPDATE app_data\n         DELETE app_data\n         sequence access\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Later:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Vault\n   |\n   +-- creates dynamic-user-123\n              |\n              +-- member of app_crud\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">26. Step 17 \u2014 Verify PostgreSQL Roles<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -c \"\\du vaultadmin\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -c \"\\du app_crud\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vaultadmin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Create role\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app_crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">without login privileges.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">27. Step 18 \u2014 Test Vault&#8217;s PostgreSQL Account<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">This test is extremely important.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PGPASSWORD='VaultAdmin2026' \\\npsql \\\n  -h 127.0.0.1 \\\n  -U vaultadmin \\\n  -d vaultlab \\\n  -c \"SELECT current_user, current_database();\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code> current_user | current_database\n--------------+-----------------\n vaultadmin   | vaultlab\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If this command fails, <strong>do not continue to Vault yet<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vault will use essentially the same type of TCP connection.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">28. Troubleshooting PostgreSQL Password Authentication<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">If you receive:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>password authentication failed\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">find the PostgreSQL HBA configuration:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -tAc \"SHOW hba_file;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then inspect it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HBA_FILE=\"$(\n  sudo -u postgres psql \\\n    -tAc \"SHOW hba_file;\" \\\n    | xargs\n)\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Display active rules:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo grep -vE '^&#91;&#91;:space:]]*(#|$)' \"$HBA_FILE\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You need an appropriate host rule for connections from:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>127.0.0.1\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">PostgreSQL client authentication is controlled by <code>pg_hba.conf<\/code>; changes require the configuration to be reloaded.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Modern PostgreSQL supports SCRAM-SHA-256 password authentication.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">29. PostgreSQL Checkpoint<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Do not continue unless all of these work:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91; ] PostgreSQL is running\n&#91; ] port 5432 is available\n&#91; ] vaultlab exists\n&#91; ] public.app_data exists\n&#91; ] vaultadmin exists\n&#91; ] app_crud exists\n&#91; ] vaultadmin has CREATEROLE\n&#91; ] vaultadmin has ADMIN OPTION on app_crud\n&#91; ] vaultadmin can connect using TCP\/password\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">30. Step 19 \u2014 Install Vault<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Add the official repository signing key:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>wget -O- https:\/\/apt.releases.hashicorp.com\/gpg \\\n  | sudo gpg --dearmor \\\n  -o \/usr\/share\/keyrings\/hashicorp-archive-keyring.gpg\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Add the repository:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>echo \\\n\"deb &#91;arch=$(dpkg --print-architecture) signed-by=\/usr\/share\/keyrings\/hashicorp-archive-keyring.gpg] https:\/\/apt.releases.hashicorp.com $(grep -oP '(?&lt;=UBUNTU_CODENAME=).*' \/etc\/os-release || lsb_release -cs) main\" \\\n| sudo tee \/etc\/apt\/sources.list.d\/hashicorp.list\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Update:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Install Vault:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install -y vault\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">31. Step 20 \u2014 Verify Vault Version<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault version\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For the current release at the time this tutorial was prepared, you should see the Vault 2.0.4 release line.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The exact build suffix may differ.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">32. Step 21 \u2014 Stop Any Existing Vault Service<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Because this tutorial uses Vault development mode:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl stop vault 2&gt;\/dev\/null || true\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pgrep -a vault || true\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ideally there should be no existing Vault process.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">33. Step 22 \u2014 Start Vault Development Server<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>nohup vault server \\\n  -dev \\\n  -dev-root-token-id='root' \\\n  -dev-listen-address='127.0.0.1:8200' \\\n  &gt; \"$LAB_DIR\/vault.log\" 2&gt;&amp;1 &amp;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Save its PID:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>echo $! &gt; \"$LAB_DIR\/vault.pid\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Wait:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sleep 2\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">34. Step 23 \u2014 Configure the Vault CLI<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export VAULT_ADDR='http:\/\/127.0.0.1:8200'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Set root token:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export VAULT_TOKEN='root'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>echo \"$VAULT_ADDR\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>http:&#47;&#47;127.0.0.1:8200\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">35. Step 24 \u2014 Verify Vault<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault status\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Important fields:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Initialized    true\nSealed         false\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">36. Step 25 \u2014 Verify Root Identity<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault token lookup\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>policies    &#91;root]\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Everything through the initial Vault configuration will be performed using the root token.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Later we remove it.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">37. Step 26 \u2014 Examine Existing Secrets Engines<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault secrets list\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Look for:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database\/\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On a fresh dev server, it should normally not exist.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">38. Step 27 \u2014 Enable the Database Secrets Engine<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault secrets enable database\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Success! Enabled the database secrets engine at: database\/\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is the documented way to enable Vault&#8217;s database secrets engine.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">39. Step 28 \u2014 Verify the Engine<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault secrets list\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should now find:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database\/\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">40. Step 29 \u2014 Configure the PostgreSQL Connection<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Create the database connection:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault write database\/config\/vaultlab-postgres \\\n  plugin_name='postgresql-database-plugin' \\\n  allowed_roles='app-crud' \\\n  connection_url='postgresql:\/\/{{username}}:{{password}}@127.0.0.1:5432\/vaultlab?sslmode=disable' \\\n  username='vaultadmin' \\\n  password='VaultAdmin2026' \\\n  password_authentication='scram-sha-256' \\\n  max_open_connections=5\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Success! Data written to: database\/config\/vaultlab-postgres\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Vault verifies the connection by default when a database configuration is created, so success here is an important integration checkpoint. The current PostgreSQL plugin supports the templated connection URL, <code>postgresql-database-plugin<\/code>, and <code>scram-sha-256<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">41. What Did That Command Do?<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">This part:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database\/config\/vaultlab-postgres\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">creates a named Vault database configuration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>plugin_name=postgresql-database-plugin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">tells Vault:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Use PostgreSQL.\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>allowed_roles=app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">means:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Only the Vault database role named app-crud\nmay use this database connection.\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{{username}}\n{{password}}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">allows Vault to insert:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vaultadmin\nVaultAdmin2026\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">without hardcoding those values directly into the URL itself.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">42. Why <code>sslmode=disable<\/code>?<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Both Vault and PostgreSQL are running on:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>127.0.0.1\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">on the same training server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Therefore this beginner lab disables PostgreSQL TLS to remove certificate configuration from the exercise.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a production database:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>use TLS\nvalidate certificates\nprotect database traffic\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The PostgreSQL Vault plugin supports PostgreSQL SSL connection parameters.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">43. Why <code>password_authentication=scram-sha-256<\/code>?<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Vault&#8217;s PostgreSQL plugin supports:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>password\nscram-sha-256\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">When configured for:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>scram-sha-256\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Vault can prepare PostgreSQL-compatible SCRAM password material rather than exposing generated passwords as plaintext password values in PostgreSQL SQL logging. PostgreSQL 10 or newer is required for this mode.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">44. Step 30 \u2014 Read the Database Configuration<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read database\/config\/vaultlab-postgres\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see information such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>allowed_roles\nconnection_details\nplugin_name\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Vault does not simply return the configured database password to you.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">45. Step 31 \u2014 List Database Connections<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault list database\/config\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Keys\n----\nvaultlab-postgres\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">46. Vault-to-PostgreSQL Checkpoint<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">At this stage:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Vault\n  |\n  |\ndatabase\/\n  |\n  |\nvaultlab-postgres\n  |\n  |\npostgresql-database-plugin\n  |\n  |\n127.0.0.1:5432\n  |\n  |\nvaultadmin\n  |\n  |\nPostgreSQL vaultlab\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The connection is working.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">47. Step 32 \u2014 Create SQL for Dynamic Accounts<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Now define what Vault should do whenever credentials are requested.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Create:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &gt; \"$LAB_DIR\/create-app-crud.sql\" &lt;&lt;'EOF'\nCREATE ROLE \"{{name}}\"\nWITH LOGIN\nPASSWORD '{{password}}'\nVALID UNTIL '{{expiration}}'\nINHERIT;\n\nGRANT app_crud TO \"{{name}}\";\nEOF\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Display:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat \"$LAB_DIR\/create-app-crud.sql\"\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">48. Understanding the Template Variables<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Vault replaces:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{{name}}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">with a dynamically generated PostgreSQL username.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vault replaces:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{{password}}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">with a dynamically generated password.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vault replaces:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{{expiration}}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">with the credential expiration time.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">HashiCorp&#8217;s current PostgreSQL plugin documents all three template fields for <code>creation_statements<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">49. Step 33 \u2014 Create Renewal SQL<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Create:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &gt; \"$LAB_DIR\/renew-app-crud.sql\" &lt;&lt;'EOF'\nALTER ROLE \"{{name}}\"\nVALID UNTIL '{{expiration}}';\nEOF\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Display:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat \"$LAB_DIR\/renew-app-crud.sql\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">When Vault renews a credential lease, this changes PostgreSQL&#8217;s:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>VALID UNTIL\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">timestamp.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The PostgreSQL plugin supports <code>renew_statements<\/code> with both <code>{{name}}<\/code> and <code>{{expiration}}<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">50. Step 34 \u2014 Create Revocation SQL<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Create:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &gt; \"$LAB_DIR\/revoke-app-crud.sql\" &lt;&lt;'EOF'\nREVOKE app_crud FROM \"{{name}}\";\nDROP ROLE IF EXISTS \"{{name}}\";\nEOF\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Display:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat \"$LAB_DIR\/revoke-app-crud.sql\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">When Vault revokes a credential:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>1. membership in app_crud is removed\n2. the dynamic PostgreSQL login is deleted\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">51. Step 35 \u2014 Create Rollback SQL<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Create:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &gt; \"$LAB_DIR\/rollback-app-crud.sql\" &lt;&lt;'EOF'\nREVOKE app_crud FROM \"{{name}}\";\nDROP ROLE IF EXISTS \"{{name}}\";\nEOF\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Rollback is useful if Vault starts creating credentials but something in the creation workflow fails.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The PostgreSQL plugin supports creation, revocation, rollback, and renewal statement types.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">52. Step 36 \u2014 Create the Vault Dynamic Database Role<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault write database\/roles\/app-crud \\\n  db_name='vaultlab-postgres' \\\n  creation_statements=@\"$LAB_DIR\/create-app-crud.sql\" \\\n  renew_statements=@\"$LAB_DIR\/renew-app-crud.sql\" \\\n  revocation_statements=@\"$LAB_DIR\/revoke-app-crud.sql\" \\\n  rollback_statements=@\"$LAB_DIR\/rollback-app-crud.sql\" \\\n  default_ttl='10m' \\\n  max_ttl='1h'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Success! Data written to: database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">HashiCorp&#8217;s current database tutorial explicitly supports loading creation statements from a file using syntax such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>creation_statements=@file.sql\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">53. Step 37 \u2014 Read the Dynamic Role<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see fields including:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>creation_statements\ndb_name\ndefault_ttl\nmax_ttl\nrenew_statements\nrevocation_statements\nrollback_statements\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">54. Step 38 \u2014 List Database Roles<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault list database\/roles\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Keys\n----\napp-crud\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">55. Step 39 \u2014 Test Dynamic Credentials as Root<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Before introducing <code>userpass<\/code>, verify that the database integration itself works.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read database\/creds\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected output resembles:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Key                Value\n---                -----\nlease_id           database\/creds\/app-crud\/...\nlease_duration     10m\nlease_renewable    true\npassword           &lt;generated-password&gt;\nusername           &lt;generated-username&gt;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Vault&#8217;s current database workflow generates credentials by reading:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database\/creds\/&lt;role&gt;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This proves:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Vault\n   \u2193\nPostgreSQL plugin\n   \u2193\nvaultadmin\n   \u2193\nCREATE ROLE\n   \u2193\nGRANT app_crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">works.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We will generate a fresh credential later as <code>student1<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">56. Step 40 \u2014 Create the Vault Policy<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Now create:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>postgresql-engine-admin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This policy gives our student complete CRUD-style access underneath:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database\/*\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and individual lease lookup, renewal, and revocation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &gt; \"$LAB_DIR\/postgresql-engine-admin.hcl\" &lt;&lt;'EOF'\n# Complete CRUD-style access to the mounted database secrets engine.\n#\n# LAB ONLY:\n# This includes database connection configuration,\n# roles, credential-generation endpoints, reset endpoints,\n# rotate-root endpoints, and other database\/* operations.\n\npath \"database\/*\" {\n  capabilities = &#91;\n    \"create\",\n    \"read\",\n    \"update\",\n    \"delete\",\n    \"list\"\n  ]\n}\n\n# Lookup a specific dynamic-secret lease.\n\npath \"sys\/leases\/lookup\" {\n  capabilities = &#91;\"update\"]\n}\n\n# Renew a specific lease.\n\npath \"sys\/leases\/renew\" {\n  capabilities = &#91;\"update\"]\n}\n\n# Revoke a specific lease.\n\npath \"sys\/leases\/revoke\" {\n  capabilities = &#91;\"update\"]\n}\nEOF\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">57. Step 41 \u2014 Examine the Vault Policy<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat \"$LAB_DIR\/postgresql-engine-admin.hcl\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The important section is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>path \"database\/*\" {\n  capabilities = &#91;\n    \"create\",\n    \"read\",\n    \"update\",\n    \"delete\",\n    \"list\"\n  ]\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That gives:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE\nREAD\nUPDATE\nDELETE\nLIST\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">capabilities inside the database secrets engine.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">HashiCorp&#8217;s current database tutorial uses the same full database-engine capability model for an administrative database policy.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">58. Important Policy Explanation<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A Vault capability does not mean every API endpoint supports every operation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database\/creds\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">is fundamentally a credential generation endpoint.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Its supported operation is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>READ\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Even though our broad wildcard policy technically grants multiple capabilities to that path, the backend endpoint itself determines which operation is valid.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Think of it as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Policy says:\n\"Student may perform these actions.\"\n\nBackend says:\n\"This particular endpoint only implements these actions.\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Both conditions must be satisfied.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">59. Step 42 \u2014 Load the Vault Policy<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault policy write \\\n  postgresql-engine-admin \\\n  \"$LAB_DIR\/postgresql-engine-admin.hcl\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Success! Uploaded policy: postgresql-engine-admin\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">60. Step 43 \u2014 Read the Vault Policy<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault policy read postgresql-engine-admin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verify the contents.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">61. Step 44 \u2014 List Vault Policies<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault policy list\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should find:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>default\npostgresql-engine-admin\nroot\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">62. Step 45 \u2014 Enable Userpass Authentication<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault auth enable userpass\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Success! Enabled userpass auth method at: userpass\/\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Vault&#8217;s current userpass auth method authenticates users from username\/password entries configured underneath:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>auth\/userpass\/users\/\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">63. Step 46 \u2014 Verify Userpass<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault auth list\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>userpass\/\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">64. Step 47 \u2014 Create One Vault User<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Create:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Username: student1\nPassword: StudentLab2026\nPolicy: postgresql-engine-admin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault write auth\/userpass\/users\/student1 \\\n  password='StudentLab2026' \\\n  policies='postgresql-engine-admin' \\\n  token_ttl='1h' \\\n  token_max_ttl='4h'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Success! Data written to: auth\/userpass\/users\/student1\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Userpass supports policy assignment and token TTL\/max-TTL configuration.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">65. Step 48 \u2014 Read the User<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Still as root:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read auth\/userpass\/users\/student1\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see information such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>token_policies\ntoken_ttl\ntoken_max_ttl\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You will not see the plaintext password returned.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">66. Step 49 \u2014 List Userpass Users<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault list auth\/userpass\/users\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Keys\n----\nstudent1\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">67. Administrative Setup Is Complete<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Root has now completed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PostgreSQL installation\n        \u2193\nDatabase creation\n        \u2193\nvaultadmin creation\n        \u2193\napp_crud PostgreSQL role\n        \u2193\nVault database engine\n        \u2193\nPostgreSQL plugin\n        \u2193\nVault DB connection\n        \u2193\nVault DB dynamic role\n        \u2193\nVault policy\n        \u2193\nuserpass\n        \u2193\nstudent1\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now we switch to the student identity.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">68. Step 50 \u2014 Remove the Root Token<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>unset VAULT_TOKEN\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verify:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>echo \"${VAULT_TOKEN:-VAULT_TOKEN is not set}\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>VAULT_TOKEN is not set\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This step is critical.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Otherwise later authorization tests would accidentally execute as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>root\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and become meaningless.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">69. Step 51 \u2014 Login as <code>student1<\/code><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault login \\\n  -method=userpass \\\n  username='student1' \\\n  password='StudentLab2026'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected output contains information similar to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>token                 &lt;generated-vault-token&gt;\ntoken_duration        1h\ntoken_renewable       true\ntoken_policies        &#91;\"default\" \"postgresql-engine-admin\"]\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The actual token is different each time.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">70. Step 52 \u2014 Verify Student Identity<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault token lookup\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should find:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>postgresql-engine-admin\ndefault\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should <strong>not<\/strong> find:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>root\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">71. Step 53 \u2014 Verify Database Role Capabilities<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault token capabilities \\\n  database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected capabilities include:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>create\ndelete\nlist\nread\nupdate\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The exact ordering may differ.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">72. Step 54 \u2014 Verify Configuration Capabilities<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault token capabilities \\\n  database\/config\/vaultlab-postgres\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Again, you should see CRUD-style capabilities.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This confirms the student has broad administrative access inside:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database\/*\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">73. Step 55 \u2014 Negative Vault Permission Test<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Vault dev mode also normally provides a KV secrets engine mounted at:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>secret\/\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Try:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault kv put \\\n  secret\/student-should-not-access \\\n  test=value\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This should fail with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>permission denied\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That failure is <strong>correct<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Our policy grants access to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database\/*\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">not:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>secret\/*\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">74. Vault Authorization Model<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">At this point:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>student1\n   |\n   | userpass login\n   v\nVault Token\n   |\n   | postgresql-engine-admin\n   v\ndatabase\/*\n   |\n   +-- config\n   +-- roles\n   +-- creds\n   +-- reset\n   +-- rotate-root\n   +-- other database engine operations\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">but:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>secret\/*\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">remains denied.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">75. Step 56 \u2014 Demonstrate CREATE<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">First delete the role that root created earlier so the student can demonstrate the complete lifecycle.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault delete database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Success! Data deleted (if it existed) at: database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Confirm:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault list database\/roles\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The role should be absent.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now CREATE it as <code>student1<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault write database\/roles\/app-crud \\\n  db_name='vaultlab-postgres' \\\n  creation_statements=@\"$LAB_DIR\/create-app-crud.sql\" \\\n  renew_statements=@\"$LAB_DIR\/renew-app-crud.sql\" \\\n  revocation_statements=@\"$LAB_DIR\/revoke-app-crud.sql\" \\\n  rollback_statements=@\"$LAB_DIR\/rollback-app-crud.sql\" \\\n  default_ttl='10m' \\\n  max_ttl='1h'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Success! Data written to: database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That demonstrates:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">76. Step 57 \u2014 Demonstrate READ<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That demonstrates:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>READ\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">77. Step 58 \u2014 Demonstrate LIST<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault list database\/roles\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Keys\n----\napp-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That demonstrates:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>LIST\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">78. Step 59 \u2014 Demonstrate UPDATE<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Change:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>default TTL\n10 minutes\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>5 minutes\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault write database\/roles\/app-crud \\\n  db_name='vaultlab-postgres' \\\n  creation_statements=@\"$LAB_DIR\/create-app-crud.sql\" \\\n  renew_statements=@\"$LAB_DIR\/renew-app-crud.sql\" \\\n  revocation_statements=@\"$LAB_DIR\/revoke-app-crud.sql\" \\\n  rollback_statements=@\"$LAB_DIR\/rollback-app-crud.sql\" \\\n  default_ttl='5m' \\\n  max_ttl='1h'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Success! Data written to: database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Read it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Confirm:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>default_ttl\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">now corresponds to five minutes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That demonstrates:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>UPDATE\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">79. Step 60 \u2014 Demonstrate DELETE<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault delete database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Success! Data deleted (if it existed) at: database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Confirm:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>No value found\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That demonstrates:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>DELETE\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">80. CRUD Checkpoint<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The student has now proven:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE\nvault write database\/roles\/app-crud\n\nREAD\nvault read database\/roles\/app-crud\n\nUPDATE\nvault write database\/roles\/app-crud\n\nLIST\nvault list database\/roles\n\nDELETE\nvault delete database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This validates the Vault ACL policy.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">81. Step 61 \u2014 Recreate the Dynamic Role<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">We need it for the rest of the lab.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault write database\/roles\/app-crud \\\n  db_name='vaultlab-postgres' \\\n  creation_statements=@\"$LAB_DIR\/create-app-crud.sql\" \\\n  renew_statements=@\"$LAB_DIR\/renew-app-crud.sql\" \\\n  revocation_statements=@\"$LAB_DIR\/revoke-app-crud.sql\" \\\n  rollback_statements=@\"$LAB_DIR\/rollback-app-crud.sql\" \\\n  default_ttl='10m' \\\n  max_ttl='1h'\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">82. Step 62 \u2014 Generate Dynamic PostgreSQL Credentials<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read database\/creds\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected output:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Key                Value\n---                -----\nlease_id           database\/creds\/app-crud\/...\nlease_duration     10m\nlease_renewable    true\npassword           ...\nusername           ...\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Every request creates a different PostgreSQL credential.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">83. Step 63 \u2014 Capture Credentials Automatically<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of copying them manually:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREDS_JSON=\"$(\n  vault read \\\n    -format=json \\\n    database\/creds\/app-crud\n)\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Extract username:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export DB_USER=\"$(\n  printf '%s' \"$CREDS_JSON\" \\\n  | jq -r '.data.username'\n)\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Extract password:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export DB_PASS=\"$(\n  printf '%s' \"$CREDS_JSON\" \\\n  | jq -r '.data.password'\n)\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Extract Vault lease ID:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export LEASE_ID=\"$(\n  printf '%s' \"$CREDS_JSON\" \\\n  | jq -r '.lease_id'\n)\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Remove the JSON copy:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>unset CREDS_JSON\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">84. Step 64 \u2014 Verify Variables<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Display the generated username:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>echo \"$DB_USER\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It may look similar to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>v-userpass-app-crud-...\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The exact format and random portion will differ.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Verify password exists without displaying it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>test -n \"$DB_PASS\" \\\n  &amp;&amp; test \"$DB_PASS\" != \"null\" \\\n  &amp;&amp; echo \"Dynamic PostgreSQL password captured.\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Dynamic PostgreSQL password captured.\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Display lease:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>echo \"$LEASE_ID\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It should resemble:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database\/creds\/app-crud\/&lt;unique-id&gt;\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">85. Step 65 \u2014 Verify the Dynamic PostgreSQL Role Exists<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">As PostgreSQL administrator:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -d postgres \\\n  -c \"SELECT rolname, rolcanlogin, rolvaliduntil FROM pg_roles WHERE rolname = '$DB_USER';\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see the generated username.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Important fields:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rolcanlogin = true\nrolvaliduntil = future timestamp\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">86. Step 66 \u2014 Verify Group Membership<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -d postgres \\\n  -c \"SELECT pg_has_role('$DB_USER', 'app_crud', 'MEMBER') AS member_of_app_crud;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code> member_of_app_crud\n--------------------\n t\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This proves Vault executed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>GRANT app_crud TO \"&lt;dynamic-user&gt;\";\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">87. Step 67 \u2014 Login to PostgreSQL Using Vault Credentials<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PGPASSWORD=\"$DB_PASS\" \\\npsql \\\n  -h 127.0.0.1 \\\n  -U \"$DB_USER\" \\\n  -d vaultlab \\\n  -c \"SELECT current_user, current_database();\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>current_user        | &lt;dynamic-user&gt;\ncurrent_database    | vaultlab\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The Vault-generated credential is working.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">88. PostgreSQL CRUD Exercise<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Now we will prove that the dynamic PostgreSQL account has:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE DATA\nREAD DATA\nUPDATE DATA\nDELETE DATA\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">permission on:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public.app_data\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">These database-level operations are separate from the earlier Vault API CRUD operations.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">89. Step 68 \u2014 READ Data with SELECT<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PGPASSWORD=\"$DB_PASS\" \\\npsql \\\n  -h 127.0.0.1 \\\n  -U \"$DB_USER\" \\\n  -d vaultlab \\\n  -c \"SELECT * FROM public.app_data;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>linux-server\nvault-server\npostgresql-server\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The dynamic user can:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SELECT\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">90. Step 69 \u2014 CREATE Data with INSERT<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PGPASSWORD=\"$DB_PASS\" \\\npsql \\\n  -h 127.0.0.1 \\\n  -U \"$DB_USER\" \\\n  -d vaultlab \\\n  -c \"INSERT INTO public.app_data (item_name, quantity) VALUES ('vault-dynamic-user', 25) RETURNING *;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault-dynamic-user | 25\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The dynamic user can:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>INSERT\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">91. Step 70 \u2014 Verify INSERT<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PGPASSWORD=\"$DB_PASS\" \\\npsql \\\n  -h 127.0.0.1 \\\n  -U \"$DB_USER\" \\\n  -d vaultlab \\\n  -c \"SELECT * FROM public.app_data WHERE item_name='vault-dynamic-user';\"\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">92. Step 71 \u2014 UPDATE Data<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PGPASSWORD=\"$DB_PASS\" \\\npsql \\\n  -h 127.0.0.1 \\\n  -U \"$DB_USER\" \\\n  -d vaultlab \\\n  -c \"UPDATE public.app_data SET quantity=50 WHERE item_name='vault-dynamic-user' RETURNING *;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected quantity:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>50\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The dynamic user can:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>UPDATE\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">93. Step 72 \u2014 DELETE Data<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PGPASSWORD=\"$DB_PASS\" \\\npsql \\\n  -h 127.0.0.1 \\\n  -U \"$DB_USER\" \\\n  -d vaultlab \\\n  -c \"DELETE FROM public.app_data WHERE item_name='vault-dynamic-user' RETURNING *;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The row should be returned and removed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The dynamic user can:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>DELETE\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">94. PostgreSQL CRUD Proven<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">We have now demonstrated:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>READ\nSELECT\n\nCREATE\nINSERT\n\nUPDATE\nUPDATE\n\nDELETE\nDELETE\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">using a PostgreSQL username and password that:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>did not exist before the request\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and were dynamically created by Vault.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">95. Step 73 \u2014 Negative PostgreSQL Test<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The dynamic account has data CRUD permissions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It should <strong>not<\/strong> be a PostgreSQL administrator.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Try creating another table:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PGPASSWORD=\"$DB_PASS\" \\\npsql \\\n  -h 127.0.0.1 \\\n  -U \"$DB_USER\" \\\n  -d vaultlab \\\n  -c \"CREATE TABLE public.should_fail (id INTEGER);\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>permission denied for schema public\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That failure is correct.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">96. Step 74 \u2014 Another Negative Test<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Try creating another PostgreSQL role:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PGPASSWORD=\"$DB_PASS\" \\\npsql \\\n  -h 127.0.0.1 \\\n  -U \"$DB_USER\" \\\n  -d vaultlab \\\n  -c \"CREATE ROLE should_fail;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>permission denied to create role\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Correct.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Therefore:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Dynamic user\n    |\n    +-- SELECT     YES\n    +-- INSERT     YES\n    +-- UPDATE     YES\n    +-- DELETE     YES\n    |\n    +-- CREATE TABLE   NO\n    +-- CREATE ROLE    NO\n    +-- SUPERUSER      NO\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">97. Step 75 \u2014 Examine the Vault Lease<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Every dynamic database credential receives a Vault lease.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault lease lookup \"$LEASE_ID\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Look for:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>expire_time\nissue_time\nrenewable\nttl\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Vault&#8217;s lease API supports lookup, renewal, and immediate revocation of dynamic secrets.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">98. Step 76 \u2014 Check PostgreSQL Expiration Before Renewal<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -d postgres \\\n  -c \"SELECT rolname, rolvaliduntil FROM pg_roles WHERE rolname = '$DB_USER';\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Take note of:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rolvaliduntil\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">99. Step 77 \u2014 Renew the Credential<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault lease renew \\\n  -increment='20m' \\\n  \"$LEASE_ID\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>lease_id          database\/creds\/app-crud\/...\nlease_duration    ...\nlease_renewable   true\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Vault is not required to honor the requested increment exactly; the role and mount TTL limits still apply. Vault&#8217;s documented lease-renew command supports this model for database credentials.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">100. Step 78 \u2014 Verify PostgreSQL Expiration Changed<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run again:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -d postgres \\\n  -c \"SELECT rolname, rolvaliduntil FROM pg_roles WHERE rolname = '$DB_USER';\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rolvaliduntil\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">timestamp should reflect the renewed lifetime.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This demonstrates:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Vault Lease Renewal\n        \u2193\nrenew_statements\n        \u2193\nALTER ROLE\n        \u2193\nPostgreSQL VALID UNTIL updated\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">101. Step 79 \u2014 Confirm the Credential Still Works<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PGPASSWORD=\"$DB_PASS\" \\\npsql \\\n  -h 127.0.0.1 \\\n  -U \"$DB_USER\" \\\n  -d vaultlab \\\n  -c \"SELECT COUNT(*) FROM public.app_data;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The query should succeed.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">102. Step 80 \u2014 Revoke the Dynamic Credential<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Now manually revoke it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault lease revoke \\\n  -sync \\\n  \"$LEASE_ID\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Success! Revoked lease: database\/creds\/app-crud\/...\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The synchronous option makes Vault wait for backend revocation rather than merely queueing it. The current lease API supports synchronous immediate revocation.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">103. What Happens During Revocation?<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Vault uses our configured:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>revocation_statements\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">which contain:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>REVOKE app_crud FROM \"{{name}}\";\nDROP ROLE IF EXISTS \"{{name}}\";\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Therefore:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Vault\n  |\n  v\nRevoke lease\n  |\n  v\nPostgreSQL plugin\n  |\n  v\nREVOKE app_crud\n  |\n  v\nDROP ROLE\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">104. Step 81 \u2014 Confirm the PostgreSQL Account Is Gone<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -d postgres \\\n  -c \"SELECT rolname FROM pg_roles WHERE rolname = '$DB_USER';\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>(0 rows)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is one of the most important results in the lab.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The account no longer exists.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">105. Step 82 \u2014 Prove the Old Credential No Longer Works<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Try:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PGPASSWORD=\"$DB_PASS\" \\\npsql \\\n  -h 127.0.0.1 \\\n  -U \"$DB_USER\" \\\n  -d vaultlab \\\n  -c \"SELECT * FROM public.app_data;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected failure similar to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FATAL: role \"&lt;dynamic-user&gt;\" does not exist\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That failure proves the credential was actually revoked in PostgreSQL.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">106. Dynamic Credential Lifecycle<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Students have now demonstrated the complete lifecycle:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>student1\n   |\n   | Vault authentication\n   v\nVault token\n   |\n   | policy permits database\/creds\/app-crud\n   v\nVault Database Engine\n   |\n   v\nPostgreSQL Plugin\n   |\n   v\nCREATE ROLE\n   |\n   v\nGRANT app_crud\n   |\n   v\nDynamic Username + Password\n   |\n   v\nPostgreSQL CRUD\n   |\n   v\nVault Lease\n   |\n   +---------------------+\n   |                     |\n   v                     v\nRenew                  Revoke\n   |                     |\n   v                     v\nALTER ROLE           REVOKE role\nVALID UNTIL          DROP ROLE\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">107. Step 83 \u2014 Generate Another Credential<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read database\/creds\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should receive:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>different username\ndifferent password\ndifferent lease ID\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is the core concept behind Vault dynamic database credentials.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Applications do not have to permanently share:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>appuser \/ permanent-password\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Instead:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Application A \u2192 credential A\nApplication B \u2192 credential B\nApplication C \u2192 credential C\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">with independent lease lifecycles.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">108. Optional \u2014 Database Connection CRUD<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Because our student policy grants:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database\/*\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">the user also has administrative access to database connection configuration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This portion is optional because deleting a connection intentionally breaks credential generation until it is recreated.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Make sure there are <strong>no active dynamic credential leases<\/strong> before performing it.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">109. READ the Connection<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read database\/config\/vaultlab-postgres\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">110. LIST Connections<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault list database\/config\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vaultlab-postgres\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">111. UPDATE the Connection<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Change maximum open connections from:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>5\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>8\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault write database\/config\/vaultlab-postgres \\\n  plugin_name='postgresql-database-plugin' \\\n  allowed_roles='app-crud' \\\n  connection_url='postgresql:\/\/{{username}}:{{password}}@127.0.0.1:5432\/vaultlab?sslmode=disable' \\\n  username='vaultadmin' \\\n  password='VaultAdmin2026' \\\n  password_authentication='scram-sha-256' \\\n  max_open_connections=8\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Success! Data written to: database\/config\/vaultlab-postgres\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The database configuration API distinguishes create and update operations.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">112. Reset the PostgreSQL Plugin Connection<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A useful administrative operation is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault write \\\n  -force \\\n  database\/reset\/vaultlab-postgres\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This closes\/restarts the underlying plugin connection while preserving its stored configuration. Vault documents the reset endpoint for database connections.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">113. DELETE the Connection<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Do this only as an exercise.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault delete database\/config\/vaultlab-postgres\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Success! Data deleted...\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Confirm:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read database\/config\/vaultlab-postgres\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>No value found\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The current Vault database API explicitly supports deleting database connections.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">114. Prove Credential Generation Is Broken<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Try:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read database\/creds\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It should now fail because:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">references:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vaultlab-postgres\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and that connection no longer exists.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This failure is expected.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">115. Recreate the Connection<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault write database\/config\/vaultlab-postgres \\\n  plugin_name='postgresql-database-plugin' \\\n  allowed_roles='app-crud' \\\n  connection_url='postgresql:\/\/{{username}}:{{password}}@127.0.0.1:5432\/vaultlab?sslmode=disable' \\\n  username='vaultadmin' \\\n  password='VaultAdmin2026' \\\n  password_authentication='scram-sha-256' \\\n  max_open_connections=5\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Success! Data written to: database\/config\/vaultlab-postgres\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read database\/creds\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">should work again.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you generate a credential during this test, revoke its lease before continuing.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">116. Complete Vault CRUD Summary<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">Database Connection<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">CREATE<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>vault write database\/config\/vaultlab-postgres ...\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">READ<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read database\/config\/vaultlab-postgres\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">LIST<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>vault list database\/config\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">UPDATE<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>vault write database\/config\/vaultlab-postgres ...\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">DELETE<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>vault delete database\/config\/vaultlab-postgres\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">117. Complete Vault Role CRUD Summary<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">CREATE<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>vault write database\/roles\/app-crud \\\n  db_name='vaultlab-postgres' \\\n  creation_statements=@\"$LAB_DIR\/create-app-crud.sql\" \\\n  renew_statements=@\"$LAB_DIR\/renew-app-crud.sql\" \\\n  revocation_statements=@\"$LAB_DIR\/revoke-app-crud.sql\" \\\n  rollback_statements=@\"$LAB_DIR\/rollback-app-crud.sql\" \\\n  default_ttl='10m' \\\n  max_ttl='1h'\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">READ<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">LIST<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>vault list database\/roles\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">UPDATE<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use the same <code>vault write<\/code> command with changed settings.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">DELETE<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>vault delete database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">118. Credential Generation<\/h1>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read database\/creds\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Response:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>lease_id\nusername\npassword\nlease_duration\nlease_renewable\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">119. Lease Lookup<\/h1>\n\n\n\n<pre class=\"wp-block-code\"><code>vault lease lookup \"$LEASE_ID\"\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">120. Lease Renewal<\/h1>\n\n\n\n<pre class=\"wp-block-code\"><code>vault lease renew \\\n  -increment='20m' \\\n  \"$LEASE_ID\"\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">121. Lease Revocation<\/h1>\n\n\n\n<pre class=\"wp-block-code\"><code>vault lease revoke \\\n  -sync \\\n  \"$LEASE_ID\"\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">122. Userpass Command Summary<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Enable:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault auth enable userpass\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault write auth\/userpass\/users\/student1 \\\n  password='StudentLab2026' \\\n  policies='postgresql-engine-admin' \\\n  token_ttl='1h' \\\n  token_max_ttl='4h'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Read:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read auth\/userpass\/users\/student1\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">List:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault list auth\/userpass\/users\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Login:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault login \\\n  -method=userpass \\\n  username='student1' \\\n  password='StudentLab2026'\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">123. Vault Policy Used in This Lab<\/h1>\n\n\n\n<pre class=\"wp-block-code\"><code>path \"database\/*\" {\n  capabilities = &#91;\n    \"create\",\n    \"read\",\n    \"update\",\n    \"delete\",\n    \"list\"\n  ]\n}\n\npath \"sys\/leases\/lookup\" {\n  capabilities = &#91;\"update\"]\n}\n\npath \"sys\/leases\/renew\" {\n  capabilities = &#91;\"update\"]\n}\n\npath \"sys\/leases\/revoke\" {\n  capabilities = &#91;\"update\"]\n}\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">124. Why This Policy Is Very Powerful<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">This policy gives the user:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database\/*\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">access.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That means more than simply requesting credentials.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The user can potentially:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>create database connections\nmodify database connections\ndelete database connections\n\ncreate Vault DB roles\nmodify Vault DB roles\ndelete Vault DB roles\n\ngenerate database credentials\n\nreset database plugin connections\n\naccess rotate-root endpoints\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Therefore this is appropriate for the requested:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>complete database secret-engine CRUD lab\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">but it is <strong>too broad for a normal application<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">125. Production-Style Consumer Policy<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">For comparison, an application usually needs something closer to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>path \"database\/creds\/app-crud\" {\n  capabilities = &#91;\"read\"]\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nothing more.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The application would not need access to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database\/config\/*\ndatabase\/roles\/*\ndatabase\/reset\/*\ndatabase\/rotate-root\/*\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">126. Production Separation of Responsibilities<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A better production design would have:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Vault Platform Administrator\n           |\n           +-- configure engine\n           +-- configure database connections\n           +-- configure roles\n           +-- manage policies\n\n\nApplication\n           |\n           +-- read database\/creds\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Do not normally give applications:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database\/*\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">administrative access.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">127. Complete Architecture<\/h1>\n\n\n\n<pre class=\"wp-block-code\"><code>                          +----------------+\n                          |    student1    |\n                          +-------+--------+\n                                  |\n                             username\/password\n                                  |\n                                  v\n                          +----------------+\n                          |    userpass    |\n                          +-------+--------+\n                                  |\n                              authenticates\n                                  |\n                                  v\n                          +----------------+\n                          |  Vault Token   |\n                          +-------+--------+\n                                  |\n                     postgresql-engine-admin\n                                  |\n                                  v\n                    +-------------------------+\n                    | database\/ Secrets Engine|\n                    +------------+------------+\n                                 |\n                                 |\n                      vaultlab-postgres\n                                 |\n                                 v\n                    +-------------------------+\n                    | PostgreSQL DB Plugin    |\n                    | postgresql-database-    |\n                    | plugin                  |\n                    +------------+------------+\n                                 |\n                                 |\n                            vaultadmin\n                                 |\n                                 v\n                      PostgreSQL 127.0.0.1\n                                 |\n                                 |\n                        CREATE dynamic role\n                                 |\n                                 v\n                +--------------------------------+\n                | v-userpass-app-crud-XXXXXXXX   |\n                +---------------+----------------+\n                                |\n                         GRANT app_crud\n                                |\n                                v\n                       +----------------+\n                       |    app_crud    |\n                       |    NOLOGIN     |\n                       +-------+--------+\n                               |\n             +-----------------+----------------+\n             |                 |                |\n           SELECT            INSERT           UPDATE\n             |                 |                |\n             +-----------------+----------------+\n                               |\n                             DELETE\n                               |\n                               v\n                      public.app_data\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">128. Authentication vs Authorization<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Students should be able to answer:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Who authenticates <code>student1<\/code>?<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>Vault userpass\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">What does student1 receive?<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>A Vault token.\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">What controls the Vault token?<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>postgresql-engine-admin policy\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">What does <code>database\/creds\/app-crud<\/code> do?<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>Generates a PostgreSQL username\/password.\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">What controls that database username&#8217;s permissions?<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>PostgreSQL app_crud role.\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">What controls how long it exists?<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>Vault lease and PostgreSQL VALID UNTIL.\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">What happens when Vault revokes it?<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>Vault executes PostgreSQL revocation statements\nand drops the dynamic PostgreSQL role.\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">129. Troubleshooting \u2014 Vault Cannot Connect to PostgreSQL<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Typical error:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>error creating database object\nconnection refused\npassword authentication failed\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">First test PostgreSQL manually:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PGPASSWORD='VaultAdmin2026' \\\npsql \\\n  -h 127.0.0.1 \\\n  -U vaultadmin \\\n  -d vaultlab \\\n  -c \"SELECT current_user;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If that fails, fix PostgreSQL first.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Check:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pg_isready\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ss -lnt | grep 5432\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check service:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl status postgresql --no-pager\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">130. Troubleshooting \u2014 Vault Database Config Fails<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Verify:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>database = vaultlab\nusername = vaultadmin\npassword = VaultAdmin2026\nhost     = 127.0.0.1\nport     = 5432\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then retry:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault write database\/config\/vaultlab-postgres \\\n  plugin_name='postgresql-database-plugin' \\\n  allowed_roles='app-crud' \\\n  connection_url='postgresql:\/\/{{username}}:{{password}}@127.0.0.1:5432\/vaultlab?sslmode=disable' \\\n  username='vaultadmin' \\\n  password='VaultAdmin2026' \\\n  password_authentication='scram-sha-256'\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">131. Troubleshooting \u2014 <code>permission denied to create role<\/code><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Check:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -c \"\\du vaultadmin\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The account must have:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Create role\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If necessary:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -c \"ALTER ROLE vaultadmin CREATEROLE;\"\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">132. Troubleshooting \u2014 <code>must have admin option<\/code><\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Check membership:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -c \"\\du vaultadmin\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Reapply:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -c \"GRANT app_crud TO vaultadmin WITH ADMIN OPTION;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">PostgreSQL&#8217;s <code>ADMIN OPTION<\/code> is what allows a role to grant\/revoke membership in another role.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">133. Troubleshooting \u2014 Dynamic User Cannot SELECT<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Check PostgreSQL privileges:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -d vaultlab \\\n  -c \"\\dp public.app_data\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Reapply:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql -d vaultlab &lt;&lt;'SQL'\nGRANT CONNECT ON DATABASE vaultlab TO app_crud;\nGRANT USAGE ON SCHEMA public TO app_crud;\nGRANT SELECT, INSERT, UPDATE, DELETE\nON TABLE public.app_data\nTO app_crud;\nGRANT USAGE, SELECT\nON SEQUENCE public.app_data_id_seq\nTO app_crud;\nSQL\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">134. Troubleshooting \u2014 INSERT Fails on Sequence<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Typical error:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>permission denied for sequence app_data_id_seq\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Fix:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -d vaultlab \\\n  -c \"GRANT USAGE, SELECT ON SEQUENCE public.app_data_id_seq TO app_crud;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then retry the INSERT.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">135. Troubleshooting \u2014 Vault Permission Denied<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault token lookup\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verify:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>postgresql-engine-admin\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">is present.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Check capability:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault token capabilities \\\n  database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>create, delete, list, read, update\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">136. Troubleshooting \u2014 Still Logged In as Root<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>echo \"${VAULT_TOKEN:-not-set}\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If it prints:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>root\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">then:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>unset VAULT_TOKEN\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Login again:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault login \\\n  -method=userpass \\\n  username='student1' \\\n  password='StudentLab2026'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault token lookup\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ensure:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>root\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">is not listed as a policy.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">137. Troubleshooting \u2014 Credential Revocation Fails<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Make sure you do not have a persistent interactive PostgreSQL session open as the generated account.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Exit it with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\\q\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then retry:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault lease revoke \\\n  -sync \\\n  \"$LEASE_ID\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check Vault logs:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>tail -100 \"$LAB_DIR\/vault.log\"\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">138. Troubleshooting \u2014 Userpass Login Fails<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Confirm user exists using root\/admin access:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault read auth\/userpass\/users\/student1\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check password carefully:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>StudentLab2026\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Repeated incorrect login attempts can interact with Vault&#8217;s user-lockout protections supported by userpass.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">139. Student Success Checklist<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A successful lab should satisfy all of these:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91; ] PostgreSQL installed\n\n&#91; ] PostgreSQL service running\n\n&#91; ] PostgreSQL accepts connections on 5432\n\n&#91; ] vaultlab database exists\n\n&#91; ] app_data table exists\n\n&#91; ] vaultadmin exists\n\n&#91; ] vaultadmin has CREATEROLE\n\n&#91; ] app_crud exists\n\n&#91; ] app_crud is NOLOGIN\n\n&#91; ] vaultadmin has ADMIN OPTION on app_crud\n\n&#91; ] vaultadmin TCP\/password login works\n\n&#91; ] Vault installed\n\n&#91; ] Vault version verified\n\n&#91; ] Vault dev server running\n\n&#91; ] Vault initialized\n\n&#91; ] Vault unsealed\n\n&#91; ] database\/ secrets engine enabled\n\n&#91; ] vaultlab-postgres connection configured\n\n&#91; ] PostgreSQL plugin connection works\n\n&#91; ] app-crud Vault DB role exists\n\n&#91; ] postgresql-engine-admin policy exists\n\n&#91; ] userpass enabled\n\n&#91; ] student1 exists\n\n&#91; ] student1 can login\n\n&#91; ] student1 has postgresql-engine-admin\n\n&#91; ] student1 does not have root\n\n&#91; ] student1 cannot access unrelated secret\/\n\n&#91; ] student1 can CREATE database role\n\n&#91; ] student1 can READ database role\n\n&#91; ] student1 can LIST database roles\n\n&#91; ] student1 can UPDATE database role\n\n&#91; ] student1 can DELETE database role\n\n&#91; ] student1 can generate PostgreSQL credentials\n\n&#91; ] dynamic PostgreSQL role exists\n\n&#91; ] dynamic account belongs to app_crud\n\n&#91; ] dynamic account can SELECT\n\n&#91; ] dynamic account can INSERT\n\n&#91; ] dynamic account can UPDATE\n\n&#91; ] dynamic account can DELETE\n\n&#91; ] dynamic account cannot CREATE TABLE\n\n&#91; ] dynamic account cannot CREATE ROLE\n\n&#91; ] credential has a Vault lease\n\n&#91; ] lease can be looked up\n\n&#91; ] lease can be renewed\n\n&#91; ] PostgreSQL VALID UNTIL changes\n\n&#91; ] lease can be revoked\n\n&#91; ] PostgreSQL dynamic role disappears\n\n&#91; ] revoked credentials no longer work\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">140. Cleanup \u2014 Revoke Any Remaining Credentials First<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Before stopping Vault, revoke every credential you created during the lab.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a known lease:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault lease revoke \\\n  -sync \\\n  \"$LEASE_ID\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If that lease was already revoked, simply continue.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">141. Delete Vault Dynamic Role<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault delete database\/roles\/app-crud\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">142. Delete Vault Database Connection<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vault delete database\/config\/vaultlab-postgres\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">143. Remove Sensitive Shell Variables<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>unset DB_USER\nunset DB_PASS\nunset LEASE_ID\nunset VAULT_TOKEN\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">144. Stop Vault<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>kill \"$(cat \"$LAB_DIR\/vault.pid\")\" 2&gt;\/dev\/null || true\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pgrep -a vault || true\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">145. Remove PostgreSQL Lab Database<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">First terminate any remaining connection to the lab database:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql &lt;&lt;'SQL'\nSELECT pg_terminate_backend(pid)\nFROM pg_stat_activity\nWHERE datname = 'vaultlab'\n  AND pid &lt;&gt; pg_backend_pid();\nSQL\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Drop database:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres dropdb \\\n  --if-exists \\\n  vaultlab\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">146. Remove PostgreSQL Lab Roles<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Remove the membership:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -c \"REVOKE app_crud FROM vaultadmin;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Drop permission role:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -c \"DROP ROLE IF EXISTS app_crud;\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Drop Vault database user:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo -u postgres psql \\\n  -c \"DROP ROLE IF EXISTS vaultadmin;\"\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">147. Remove Vault CLI Token File<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Vault login may have created:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>~\/.vault-token\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Remove it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rm -f \"$HOME\/.vault-token\"\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">148. Remove Lab Files<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rm -rf \"$LAB_DIR\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>unset LAB_DIR\nunset VAULT_ADDR\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">149. Final Learning Flow<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Students should remember this sequence:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PostgreSQL\n    \u2193\nCreate vaultadmin\n    \u2193\nCreate app_crud permission role\n    \u2193\nCreate vaultlab database\n    \u2193\nCreate app_data table\n    \u2193\nGrant CRUD to app_crud\n    \u2193\nVault\n    \u2193\nEnable database secrets engine\n    \u2193\nConfigure PostgreSQL plugin\n    \u2193\nCreate dynamic database role\n    \u2193\nEnable userpass\n    \u2193\nCreate student1\n    \u2193\nAttach postgresql-engine-admin\n    \u2193\nLogin as student1\n    \u2193\nCreate \/ Read \/ Update \/ List \/ Delete Vault DB role\n    \u2193\nGenerate dynamic PostgreSQL credential\n    \u2193\nVault creates temporary PostgreSQL login\n    \u2193\nVault grants app_crud\n    \u2193\nTemporary user performs SELECT \/ INSERT \/ UPDATE \/ DELETE\n    \u2193\nVault tracks credential with lease\n    \u2193\nRenew lease\n    \u2193\nPostgreSQL VALID UNTIL changes\n    \u2193\nRevoke lease\n    \u2193\nPostgreSQL dynamic user is deleted\n    \u2193\nOld username\/password stops working\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">150. The Most Important Concept<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Before Vault:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Application\n     |\n     |\nPermanent Username\nPermanent Password\n     |\n     v\nPostgreSQL\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The credential may exist for:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>months\nyears\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and may be shared by many applications.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With Vault:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Application\n     |\n     | authenticate\n     v\nVault\n     |\n     | request credentials\n     v\nDynamic Username\nDynamic Password\n     |\n     | short TTL\n     v\nPostgreSQL\n     |\n     | lease expires\/revoked\n     v\nCredential removed\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is the fundamental benefit of Vault&#8217;s PostgreSQL database secrets engine.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">151. Final Architecture Summary<\/h1>\n\n\n\n<pre class=\"wp-block-code\"><code>                      USER \/ STUDENT\n                           |\n                           |\n                     student1\/password\n                           |\n                           v\n                    +--------------+\n                    |   USERPASS   |\n                    +------+-------+\n                           |\n                           v\n                    +--------------+\n                    | VAULT TOKEN  |\n                    +------+-------+\n                           |\n                  postgresql-engine-admin\n                           |\n                           v\n             +----------------------------+\n             | Database Secrets Engine    |\n             | database\/                  |\n             +-------------+--------------+\n                           |\n                    vaultlab-postgres\n                           |\n                           v\n             +----------------------------+\n             | PostgreSQL Plugin          |\n             | postgresql-database-plugin |\n             +-------------+--------------+\n                           |\n                       vaultadmin\n                           |\n                           v\n                +----------------------+\n                | PostgreSQL           |\n                | vaultlab             |\n                +----------+-----------+\n                           |\n                  create dynamic user\n                           |\n                           v\n                +----------------------+\n                | Temporary DB Login   |\n                +----------+-----------+\n                           |\n                     member of\n                           |\n                           v\n                +----------------------+\n                | app_crud             |\n                | PostgreSQL Role      |\n                +----------+-----------+\n                           |\n               SELECT \/ INSERT \/ UPDATE\n                        \/ DELETE\n                           |\n                           v\n                +----------------------+\n                | public.app_data      |\n                +----------------------+\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">152. Validation Notes<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The Vault-specific commands and endpoint structure in this tutorial were checked against the current Vault <strong>2.x<\/strong> documentation, including the Vault 2.0.4 release information, database secrets-engine API, PostgreSQL database plugin API, current <code>userpass<\/code> API, and current lease management API.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The PostgreSQL permission design uses documented PostgreSQL roles, <code>CREATEROLE<\/code>, role membership, and <code>WITH ADMIN OPTION<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The dynamic-role pattern follows HashiCorp&#8217;s current recommended PostgreSQL approach of creating a PostgreSQL group\/permission role and granting that role to Vault-generated login accounts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The important difference from many older Vault tutorials is that this guide explicitly configures PostgreSQL creation, renewal, rollback, and revocation behavior instead of blindly depending on generic backend defaults. HashiCorp specifically warns that generic database statements may be unsuitable for production database deployments.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The exact commands are designed for a single Ubuntu server with PostgreSQL and Vault running locally. Package versions, Ubuntu PostgreSQL paths, and exact command output formatting can vary between Ubuntu releases, so the included verification checkpoints should be treated as mandatory parts of the student exercise rather than skipped.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Complete Beginner-Friendly Hands-On Tutorial for Ubuntu Linux 1. Lab Objective In this lab we will build the following environment on a single Ubuntu Linux server: By the&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1044","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts\/1044","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/comments?post=1044"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts\/1044\/revisions"}],"predecessor-version":[{"id":1045,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts\/1044\/revisions\/1045"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/media?parent=1044"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/categories?post=1044"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/tags?post=1044"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}