{"id":8077,"date":"2026-07-19T22:24:17","date_gmt":"2026-07-20T04:24:17","guid":{"rendered":"https:\/\/gregsowell.com\/?p=8077"},"modified":"2026-07-19T22:27:19","modified_gmt":"2026-07-20T04:27:19","slug":"hashicorp-vault-enterprise-with-ansible-automation-platform-2-6","status":"publish","type":"post","link":"https:\/\/gregsowell.com\/?p=8077","title":{"rendered":"Hashicorp Vault Enterprise with Ansible Automation Platform 2.6"},"content":{"rendered":"<p><a href=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/07\/ChatGPT-Image-Jul-17-2026-10_44_16-PM.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/07\/ChatGPT-Image-Jul-17-2026-10_44_16-PM-300x169.png\" alt=\"\" width=\"300\" height=\"169\" class=\"aligncenter size-medium wp-image-8101\" srcset=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/07\/ChatGPT-Image-Jul-17-2026-10_44_16-PM-300x169.png 300w, https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/07\/ChatGPT-Image-Jul-17-2026-10_44_16-PM-1024x576.png 1024w, https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/07\/ChatGPT-Image-Jul-17-2026-10_44_16-PM-768x432.png 768w, https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/07\/ChatGPT-Image-Jul-17-2026-10_44_16-PM-1536x864.png 1536w, https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/07\/ChatGPT-Image-Jul-17-2026-10_44_16-PM.png 1672w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><br \/>\nBelieve it or not, I covered connecting HashiCorp Vault to Ansible Tower about five years ago. Things have changed since then; it&#8217;s Ansible Automation Platform 2.6 now, not Tower, but the core idea holds up just fine moving forward: don&#8217;t store secrets in AAP at all. Store them in Vault, and let AAP reach out and grab them at run time.<\/p>\n<p>A Vault <strong>Secrets Engine<\/strong> is really just a server off to the side that you query to pull secrets from: passwords, API keys, tokens, SSH keys, I mean literally anything you want to store in there. And AAP is happy to use it. So let&#8217;s walk through the whole thing, Vault side first, then over to AAP, and I&#8217;ll give you the CLI commands along the way for the parts that the Vault UI can&#8217;t actually do for you.<\/p>\n<h2>Video Demo<\/h2>\n<p><iframe loading=\"lazy\" width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/_RSEWX8_8EE?si=Ogpig_cb3eUoo0cD\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<h2>Playbook Repo<\/h2>\n<p><a href=\"https:\/\/github.com\/gregsowell\/hashi-vault-aap-demo\">Here&#8217;s a quick link to my playbook repository.<\/a><\/p>\n<h2>Setting Up the Secrets Engine (KV) in Vault<\/h2>\n<p>Over in Vault, click into <strong style=\"font-weight:700;\">Secrets Engine<\/strong> and hit <strong style=\"font-weight:700;\">Enable Engine<\/strong>. Pick <strong style=\"font-weight:700;\">KV<\/strong> from the Generic section, and give it a path. Mine is just <code>secret<\/code>. While you&#8217;re in there, expand Method Options, and in the <strong style=\"font-weight:700;\">Version<\/strong> dropdown select <code>2<\/code>. You want the newer KV engine, not the original, since it&#8217;s what gives you versioned secrets and the metadata endpoints we&#8217;ll lean on later. Scroll down and click <strong style=\"font-weight:700;\">Enable Engine<\/strong> to finish.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/07\/01-vault-kv-secret-rhel-hosts-1024x576.jpg\" width=\"1024\" height=\"576\" style=\"max-width:100%;height:auto;\" alt=\"HashiCorp Vault KV secret rhel_hosts with root_password subkey\" \/><\/p>\n<h2>Storing the Secrets<\/h2>\n<p>Once the engine is up, click <strong style=\"font-weight:700;\">Create secret<\/strong> and give it a path. I named mine <code>rhel_hosts<\/code>. Inside it I added two keys:<\/p>\n<ul>\n<li><code>root_password<\/code>, the value a machine credential will use to log into the target host<\/li>\n<li><code>special_key<\/code>, just a second value to prove you can pull more than one secret out of the same path<\/li>\n<\/ul>\n<p>Click <strong style=\"font-weight:700;\">Save<\/strong> and you&#8217;re done. A single secret path can hold as many key\/value pairs as you want.<\/p>\n<p>If you&#8217;d rather sanity check it from the command line instead of clicking around the UI, SSH into your Vault box and run:<\/p>\n<pre class=\"gs-code\"><code class=\"language-bash\">vault kv get secret\/rhel_hosts<\/code><\/pre>\n<p>You should see both <code>root_password<\/code> and <code>special_key<\/code> come back in the output.<\/p>\n<h2>Locking Down Access with an ACL Policy<\/h2>\n<p>Before anything can read that secret, it needs permission. That&#8217;s what a policy is for. Click <strong style=\"font-weight:700;\">Policies<\/strong>, then <strong style=\"font-weight:700;\">ACL Policies<\/strong> in the left menu, then <strong style=\"font-weight:700;\">Create ACL policy<\/strong> (or edit an existing one, which is what I did since I already had a policy named <code>aap-terraform-policy<\/code> from using this same Vault for Terraform). Paste in a policy body like this:<\/p>\n<pre class=\"gs-code\"><code class=\"language-hcl\"># Read the rhel_hosts secret\r\npath &quot;secret\/data\/rhel_hosts&quot; {\r\n  capabilities = [&quot;read&quot;]\r\n}\r\n\r\n# Let AAP see, list, and read metadata for that secret\r\npath &quot;secret\/metadata\/rhel_hosts&quot; {\r\n  capabilities = [&quot;list&quot;, &quot;read&quot;]\r\n}\r\n\r\n# Allow the AppRole to create and manage its own child tokens\r\npath &quot;auth\/token\/create&quot; {\r\n  capabilities = [&quot;create&quot;, &quot;update&quot;]\r\n}\r\n\r\npath &quot;auth\/token\/renew-self&quot; {\r\n  capabilities = [&quot;update&quot;]\r\n}\r\n\r\npath &quot;auth\/token\/lookup-self&quot; {\r\n  capabilities = [&quot;read&quot;]\r\n}<\/code><\/pre>\n<p>That first block is the one that actually matters for this demo. It grants read-only access to <code>secret\/data\/rhel_hosts<\/code>, nothing more. The token blocks underneath just make sure the AppRole token AAP gets back can renew and look up itself properly. If you end up storing more than one secret path, add another <code>path<\/code> block the same way, one per path. Click <strong style=\"font-weight:700;\">Create policy<\/strong> when you&#8217;re done.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/07\/03-vault-acl-policy-rhel-hosts-1024x576.jpg\" width=\"1024\" height=\"576\" style=\"max-width:100%;height:auto;\" alt=\"Vault ACL policy editor with secret\/data\/rhel_hosts read capability added\" \/><\/p>\n<h2>Enabling AppRole Authentication<\/h2>\n<p>Now, click <strong style=\"font-weight:700;\">Access<\/strong> in the left menu, then <strong style=\"font-weight:700;\">Authentication Methods<\/strong>, then <strong style=\"font-weight:700;\">Enable new method<\/strong> on the right. Pick the <strong style=\"font-weight:700;\">AppRole<\/strong> tile. Leave the path at its default of <code>approle<\/code> and click <strong style=\"font-weight:700;\">Enable method<\/strong>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/07\/02-vault-approle-auth-method-1024x576.jpg\" width=\"1024\" height=\"576\" style=\"max-width:100%;height:auto;\" alt=\"Vault Authentication Methods page showing approle enabled\" \/><\/p>\n<p>You could authenticate with a plain username and password instead, but AppRole lets you get a lot more granular. You can restrict things like which IP ranges are allowed to authenticate. For a machine-to-machine integration like this one, it&#8217;s the right call.<\/p>\n<p>Here&#8217;s the catch, though. The Vault UI only supports enabling the AppRole method. Actually creating the role itself, the thing AAP will authenticate as, has to happen from the Vault CLI. This is the part of the video I said gets a little more involved.<\/p>\n<h2>Creating the AppRole from the CLI<\/h2>\n<p>SSH into your Vault server and point the CLI at your Vault address:<\/p>\n<pre class=\"gs-code\"><code class=\"language-bash\">ssh rhel@vault\r\n\r\nexport VAULT_ADDR=http:\/\/127.0.0.1:8200\r\nvault login -address=$VAULT_ADDR -method=userpass username=admin<\/code><\/pre>\n<p>It&#8217;ll prompt you for the admin password interactively, so you&#8217;re not typing it in plain text on the command line. Once you&#8217;re logged in, create the role and tie it to the policy you just wrote:<\/p>\n<pre class=\"gs-code\"><code class=\"language-bash\">vault write auth\/approle\/role\/aap token_policies=&quot;aap-terraform-policy&quot;<\/code><\/pre>\n<p>You should get back <code>Success! Data written to: auth\/approle\/role\/aap<\/code>. Now pull the Role ID:<\/p>\n<pre class=\"gs-code\"><code class=\"language-bash\">vault read auth\/approle\/role\/aap\/role-id<\/code><\/pre>\n<p>And generate a Secret ID for it:<\/p>\n<pre class=\"gs-code\"><code class=\"language-bash\">vault write -f auth\/approle\/role\/aap\/secret-id<\/code><\/pre>\n<p>Copy both of those values somewhere safe. You&#8217;ll need them in just a second over in AAP. If you want to prove the AppRole actually works before you leave the terminal, test it right here:<\/p>\n<pre class=\"gs-code\"><code class=\"language-bash\">vault write auth\/approle\/login \\\r\n  role_id=&lt;role-id-from-above&gt; \\\r\n  secret_id=&lt;secret-id-from-above&gt;<\/code><\/pre>\n<p>If that comes back with a token and a set of key\/value pairs instead of an error, the AppRole is good to go.<\/p>\n<h2>What It Looks Like Before Vault Is Wired Up<\/h2>\n<p>Back in AAP, I have a job template called <code>gregsowell-vault-test<\/code> that uses two credentials pulling from Vault: a machine credential for login, and a custom &#8220;Special Key&#8221; credential just to prove a second secret can flow through. Before those credentials are actually mapped to anything in Vault, running the template fails immediately:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/07\/04-aap-job-template-fails-1024x576.jpg\" width=\"1024\" height=\"576\" style=\"max-width:100%;height:auto;\" alt=\"AAP job template failing with invalid or incorrect password before Vault is configured\" \/><\/p>\n<p>That&#8217;s expected, since the credential fields are still empty. Everything from here on is what makes that job succeed.<\/p>\n<h2>Creating the HashiCorp Vault Secret Lookup Credential in AAP<\/h2>\n<p>In AAP, click <strong style=\"font-weight:700;\">Automation Execution<\/strong> in the left menu, then <strong style=\"font-weight:700;\">Infrastructure<\/strong>, then <strong style=\"font-weight:700;\">Credentials<\/strong>, then <strong style=\"font-weight:700;\">Create credential<\/strong> on the right. For <strong style=\"font-weight:700;\">Credential Type<\/strong>, select <strong style=\"font-weight:700;\">HashiCorp Vault Secret Lookup<\/strong>. Think of this credential as a proxy. It&#8217;s not tied to any one secret, it&#8217;s just how AAP authenticates to Vault, and every other credential that needs a Vault-backed value will hop through this one. Fill in:<\/p>\n<ul>\n<li><strong style=\"font-weight:700;\">Server URL<\/strong>, how AAP reaches your Vault instance<\/li>\n<li><strong style=\"font-weight:700;\">AppRole role_id<\/strong> and <strong style=\"font-weight:700;\">AppRole secret_id<\/strong>, the two values you copied from the CLI a minute ago<\/li>\n<li><strong style=\"font-weight:700;\">Path to Auth<\/strong>, set to <code>approle<\/code>, matching the mount path we enabled<\/li>\n<li><strong style=\"font-weight:700;\">API Version<\/strong>, set to <code>v2<\/code>, since that&#8217;s the KV engine version we enabled<\/li>\n<\/ul>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/07\/05-aap-vault-lookup-credential-1024x576.jpg\" width=\"1024\" height=\"576\" style=\"max-width:100%;height:auto;\" alt=\"AAP HashiCorp Vault Secret Lookup credential showing Server URL and AppRole fields\" \/><\/p>\n<p>Click <strong style=\"font-weight:700;\">Create credential<\/strong> to save it, then go back in and click <strong style=\"font-weight:700;\">Test<\/strong> to make sure it can actually reach Vault and authenticate. Since we already created the secret and the policy that lets this role read it, that test should come back green.<\/p>\n<h2>Building a Custom Credential Type for Extra Secrets<\/h2>\n<p>The machine credential&#8217;s password field is a first-class citizen. AAP already knows how to map it to an external secret. But what if you want to pull an arbitrary value out of Vault that doesn&#8217;t correspond to a built-in credential field? That&#8217;s what a custom credential type is for.<\/p>\n<p>I created one called <code>special_key<\/code> with a single input field. The important part is the injector configuration, which controls how that value gets used once it&#8217;s retrieved. Mine injects it as an extra var:<\/p>\n<pre class=\"gs-code\"><code class=\"language-yaml\">extra_vars:\r\n  special_key: &#039;{{ special_key }}&#039;<\/code><\/pre>\n<p>Extra vars sit at the top of Ansible&#8217;s variable precedence, so this will override any variable of the same name defined elsewhere.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/07\/06-aap-special-key-credential-type-1024x576.jpg\" width=\"1024\" height=\"576\" style=\"max-width:100%;height:auto;\" alt=\"AAP custom credential type special_key showing input and injector configuration\" \/><\/p>\n<h2>Mapping Individual Credential Fields to Vault Secrets<\/h2>\n<p>This is the step that actually ties everything together. Individual credentials don&#8217;t automatically know which key inside a Vault secret they should pull. You have to map each field explicitly.<\/p>\n<p>Edit the credential (starting with the machine credential), find the field you want populated from Vault, and click the small key icon next to it. That opens the Secret Management System picker:<\/p>\n<ol>\n<li>Under <strong style=\"font-weight:700;\">Select external credential<\/strong>, choose the Vault lookup credential you just created<\/li>\n<li>Fill out the metadata fields: <strong style=\"font-weight:700;\">Name of Secret Backend<\/strong> (<code>secret<\/code>), <strong style=\"font-weight:700;\">Path to Secret<\/strong> (<code>rhel_hosts<\/code>), <strong style=\"font-weight:700;\">Path to Auth<\/strong> (<code>approle<\/code>), and <strong style=\"font-weight:700;\">Key Name<\/strong> (<code>root_password<\/code>)<\/li>\n<li>Click <strong style=\"font-weight:700;\">Test<\/strong> before saving. It&#8217;ll catch a typo immediately instead of leaving you to debug a failed job later<\/li>\n<li>Click <strong style=\"font-weight:700;\">Finish<\/strong>, then save the credential<\/li>\n<\/ol>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/07\/07-aap-credential-vault-mapping-test-passed-1024x576.jpg\" width=\"1024\" height=\"576\" style=\"max-width:100%;height:auto;\" alt=\"AAP Secret Management System metadata form with test passed for the machine credential\" \/><\/p>\n<p>Repeat the exact same process for the Special Key credential. Same Vault lookup credential, same secret path, but with <strong style=\"font-weight:700;\">Key Name<\/strong> set to <code>special_key<\/code> instead.<\/p>\n<h2>Testing It All Out<\/h2>\n<p>With both credentials mapped, I fired off the same job template again. This time it authenticates to Vault, runs a quick <code>hostname<\/code> command, and displays the special key value:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/07\/08-aap-job-template-success-1024x576.jpg\" width=\"1024\" height=\"576\" style=\"max-width:100%;height:auto;\" alt=\"AAP job template output showing hostname is vault and the special key value\" \/><\/p>\n<p>The hostname comes back as <code>vault<\/code>, the name of the target machine, and the special key prints out exactly what we stored in the secrets engine. Neither value was ever typed into AAP.<\/p>\n<h2>Why This Is Worth the Setup<\/h2>\n<p>Once these credentials are mapped, you never have to touch them again. Rotate a password or key inside Vault, and AAP automatically inherits the new value on the next run. There&#8217;s nothing to update on the AAP side.<\/p>\n<p>This also plays really well with configuration as code. When you rebuild your AAP configuration from code, none of it contains plaintext secrets, because none of it ever did. The credential objects just carry the Vault path mappings. The only genuinely sensitive values that have to exist anywhere in your code are the AppRole role_id and secret_id on that one Vault lookup credential, and even those can be handled separately from the rest of your config.<\/p>\n<p>It&#8217;s a small amount of setup for a real reduction in what your security team has to worry about.<\/p>\n<h2>Additional Resources<\/h2>\n<ul>\n<li><a href=\"https:\/\/docs.redhat.com\/en\/documentation\/red_hat_ansible_automation_platform\/2.6\/secure-integrate_third_party_secret_management_systems\" target=\"_blank\" rel=\"noopener\">Integrate third-party secret management systems, AAP 2.6 docs<\/a><\/li>\n<li><a href=\"https:\/\/docs.redhat.com\/en\/documentation\/red_hat_ansible_automation_platform\/2.6\/html\/configuring_automation_execution\/assembly-controller-secret-management\" target=\"_blank\" rel=\"noopener\">Secret management system, AAP 2.6 Configuring automation execution<\/a><\/li>\n<li><a href=\"https:\/\/developer.hashicorp.com\/vault\/docs\/auth\/approle\" target=\"_blank\" rel=\"noopener\">AppRole auth method, HashiCorp Vault docs<\/a><\/li>\n<\/ul>\n<p>If you&#8217;re setting this up differently in your own environment, or you&#8217;ve found a cleaner way to handle any of these steps, I&#8217;d love to hear about it in the comments. Happy automating, happy secrets engineering.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Believe it or not, I covered connecting HashiCorp Vault to Ansible Tower about five years ago. Things have changed since then; it&#8217;s Ansible Automation\u2026<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[49,14,13],"tags":[],"class_list":["post-8077","post","type-post","status-publish","format-standard","hentry","category-ansible","category-linux","category-server"],"_links":{"self":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts\/8077","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=8077"}],"version-history":[{"count":11,"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts\/8077\/revisions"}],"predecessor-version":[{"id":8102,"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts\/8077\/revisions\/8102"}],"wp:attachment":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=8077"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=8077"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=8077"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}