Files
nomad/website/content/docs/secure/workload-identity/vault.mdx
Aimee Ukasick 53b083b8c5 Docs: Nomad IA (#26063)
* Move commands from docs to its own root-level directory

* temporarily use modified dev-portal branch with nomad ia changes

* explicitly clone nomad ia exp branch

* retrigger build, fixed dev-portal broken build

* architecture, concepts and get started individual pages

* fix get started section destinations

* reference section

* update repo comment in website-build.sh to show branch

* docs nav file update capitalization

* update capitalization to force deploy

* remove nomad-vs-kubernetes dir; move content to what is nomad pg

* job section

* Nomad operations category, deploy section

* operations category, govern section

* operations - manage

* operations/scale; concepts scheduling fix

* networking

* monitor

* secure section

* remote auth-methods folder and move up pages to sso; linkcheck

* Fix install2deploy redirects

* fix architecture redirects

* Job section: Add missing section index pages

* Add section index pages so breadcrumbs build correctly

* concepts/index fix front matter indentation

* move task driver plugin config to new deploy section

* Finish adding full URL to tutorials links in nav

* change SSO to Authentication in nav and file system

* Docs NomadIA: Move tutorials into NomadIA branch (#26132)

* Move governance and policy from tutorials to docs

* Move tutorials content to job-declare section

* run jobs section

* stateful workloads

* advanced job scheduling

* deploy section

* manage section

* monitor section

* secure/acl and secure/authorization

* fix example that contains an unseal key in real format

* remove images from sso-vault

* secure/traffic

* secure/workload-identities

* vault-acl change unseal key and root token in command output sample

* remove lines from sample output

* fix front matter

* move nomad pack tutorials to tools

* search/replace /nomad/tutorials links

* update acl overview with content from deleted architecture/acl

* fix spelling mistake

* linkcheck - fix broken links

* fix link to Nomad variables tutorial

* fix link to Prometheus tutorial

* move who uses Nomad to use cases page; move spec/config shortcuts

add dividers

* Move Consul out of Integrations; move namespaces to govern

* move integrations/vault to secure/vault; delete integrations

* move ref arch to docs; rename Deploy Nomad back to Install Nomad

* address feedback

* linkcheck fixes

* Fixed raw_exec redirect

* add info from /nomad/tutorials/manage-jobs/jobs

* update page content with newer tutorial

* link updates for architecture sub-folders

* Add redirects for removed section index pages. Fix links.

* fix broken links from linkcheck

* Revert to use dev-portal main branch instead of nomadIA branch

* build workaround: add intro-nav-data.json with single entry

* fix content-check error

* add intro directory to get around Vercel build error

* workound for emtpry directory

* remove mdx from /intro/ to fix content-check and git snafu

* Add intro index.mdx so Vercel build should work

---------

Co-authored-by: Tu Nguyen <im2nguyen@gmail.com>
2025-07-08 19:24:52 -05:00

1344 lines
37 KiB
Plaintext

---
layout: docs
page_title: Use workload identities with Vault
description: |-
Configure Vault to accept Nomad workload identities, run a Nomad job to read
secrets from Vault, configure dynamic secrets in Vault, and run a job with a
custom Vault ACL role.
---
# Use workload identities with Vault
Nomad integrates with Vault to retrieve [static][vault_static] and
[dynamic][vault_dyn] secrets for workloads.
Production deployments of Nomad and Vault must always run with the Access
Control List (ACL) system enabled since it protects against unauthorized
access to the cluster. When ACLs are enabled, both Nomad and Vault must be
properly configured in order for their integrations to work.
Nomad can generate [workload identities][nomad_wid] for tasks, which are
represented as [JSON Web Tokens (JWT)][jwt] signed by Nomad. These identities
can be used as proof to third parties that a workload was actually created and
is managed by Nomad. If the third party is configured to trust Nomad, it can
automatically grant specific access and permissions to Nomad workloads.
In this guide, you will:
- Start a Nomad and Vault agent with ACL enabled.
- Generate ACL tokens to access Nomad and Vault.
- Configure Vault to accept workload identities from Nomad.
- Configure Nomad to automatically generate and sign workload identities for
tasks that need access to Vault.
- Deploy sample Nomad jobs that interact with Vault.
## Prerequisites
This guide requires you to have basic familiarity with Nomad and Vault. If
you are new to these tools, complete the [Nomad Get Started][nomad_gs]
and [Vault Get Started][vault_gs] tutorials before following this one.
You will need the following tools installed:
- [Nomad 1.7 or later installed locally][nomad_install]
- [Vault 1.12 or later installed locally][vault_install]
- [Docker installed and running locally][docker_install]
## Start the Vault agent
Start a Vault dev server.
```shell-session
$ vault server -dev
==> Vault server configuration:
Administrative Namespace:
Api Address: http://127.0.0.1:8200
Cgo: disabled
Cluster Address: https://127.0.0.1:8201
Environment Variables: CLICOLOR, COLORTERM, COMMAND_MODE, EDITOR, GODEBUG, GOPATH, HOME, LANG, LC_ALL, LOGNAME, LSCOLORS, LaunchInstanceID, OLDPWD, PATH, PWD, SECURITYSESSIONID, SHELL, SHLVL, SSH_AUTH_SOCK, TERM, TERM_PROGRAM, TERM_PROGRAM_VERSION, TMPDIR, USER, WINDOWID, XPC_FLAGS, XPC_SERVICE_NAME, _, __CFBundleIdentifier, __CF_USER_TEXT_ENCODING
Go Version: go1.21.3
Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
Log Level:
Mlock: supported: false, enabled: false
Recovery Mode: false
Storage: inmem
Version: Vault v1.15.2, built 2023-11-06T11:33:28Z
Version Sha: cf1b5cafa047bc8e4a3f93444fcb4011593b92cb
==> Vault server started! Log data will stream in below:
2023-11-20T20:08:27.583-0500 [INFO] proxy environment: http_proxy="" https_proxy="" no_proxy=""
2023-11-20T20:08:27.583-0500 [INFO] incrementing seal generation: generation=1
...
WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.
You may need to set the following environment variables:
$ export VAULT_ADDR='http://127.0.0.1:8200'
...
```
<Note title="Dev Agents">
This guide uses development agents for Vault and [Nomad][nomad_dev] as a
quick way to get started. Dev agents have ephemeral state and should not be
used in production environments. They also run in the foreground of your
terminal so do not close the terminal window or you will need to rerun the
agent configuration steps again.
</Note>
Copy the value for `Root Token`.
Open another terminal window in the same directory and set the root token as
the environment variable `VAULT_TOKEN`. This terminal will act as the main
terminal session where you will run commands.
<Tabs>
<Tab heading="Linux" group="linux">
```shell-session
$ export VAULT_TOKEN=...
```
</Tab>
<Tab heading="macOS" group="mac">
```shell-session
$ export VAULT_TOKEN=...
```
</Tab>
<Tab heading="Windows" group="win">
```shell-session
$ $Env:VAULT_TOKEN = "..."
```
</Tab>
</Tabs>
Set the environment variable `VAULT_ADDR`.
<Tabs>
<Tab heading="Linux" group="linux">
```shell-session
$ export VAULT_ADDR='http://127.0.0.1:8200'
```
</Tab>
<Tab heading="macOS" group="mac">
```shell-session
$ export VAULT_ADDR='http://127.0.0.1:8200'
```
</Tab>
<Tab heading="Windows" group="win">
```shell-session
$ $Env:VAULT_ADDR = "http://127.0.0.1:8200"
```
</Tab>
</Tabs>
## Start the Nomad agent
Create a file named `nomad.hcl`. Add the following contents to it and save the
file.
<Tabs>
<Tab heading="Linux" group="linux">
<CodeBlockConfig filename="nomad.hcl">
```hcl
acl {
enabled = true
}
vault {
enabled = true
address = "http://127.0.0.1:8200"
default_identity {
aud = ["vault.io"]
ttl = "1h"
}
}
```
</CodeBlockConfig>
</Tab>
<Tab heading="macOS" group="mac">
<CodeBlockConfig filename="nomad.hcl">
```hcl
acl {
enabled = true
}
vault {
enabled = true
address = "http://127.0.0.1:8200"
default_identity {
aud = ["vault.io"]
ttl = "1h"
}
}
```
</CodeBlockConfig>
</Tab>
<Tab heading="Windows" group="win">
<CodeBlockConfig filename="nomad.hcl">
```hcl
acl {
enabled = true
}
vault {
enabled = true
address = "http://127.0.0.1:8200"
default_identity {
aud = ["vault.io"]
ttl = "1h"
}
}
plugin "docker" {
config {
allow_caps = [
"CHOWN", "DAC_OVERRIDE", "FSETID", "FOWNER", "MKNOD",
"SETGID", "SETUID", "SETFCAP", "SETPCAP", "NET_BIND_SERVICE",
"SYS_CHROOT", "KILL", "AUDIT_WRITE", "NET_RAW",
]
}
}
```
</CodeBlockConfig>
</Tab>
</Tabs>
The [`vault`][nomad_config_vault] block in this configuration file provides
the information necessary for Nomad to connect to Vault.
It also defines a default workload identity, `default_identity`, that is
automatically added to jobs that need access to Vault. Without this identity,
you would need to define an `identity` block in your jobs for every task that
needs access to Vault.
Open another terminal window in the same directory and start the Nomad dev
agent.
<Tabs>
<Tab heading="Linux" group="linux">
```shell-session
$ sudo nomad agent -dev -config 'nomad.hcl'
==> Loaded configuration from nomad.hcl
==> Starting Nomad agent...
==> Nomad agent configuration:
Advertise Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
Bind Addrs: HTTP: [127.0.0.1:4646]; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
Client: true
Log Level: DEBUG
Node Id: af9bef00-2e83-b704-2d6c-c62bc005431f
Region: global (DC: dc1)
Server: true
Version: 1.7.0
==> Nomad agent started! Log data will stream in below:
...
```
</Tab>
<Tab heading="macOS" group="mac">
```shell-session
$ nomad agent -dev -config 'nomad.hcl' -network-interface '{{GetDefaultInterfaces | attr "name"}}'
==> Loaded configuration from nomad.hcl
==> Starting Nomad agent...
==> Nomad agent configuration:
Advertise Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
Bind Addrs: HTTP: [127.0.0.1:4646]; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
Client: true
Log Level: DEBUG
Node Id: af9bef00-2e83-b704-2d6c-c62bc005431f
Region: global (DC: dc1)
Server: true
Version: 1.7.0
==> Nomad agent started! Log data will stream in below:
...
```
</Tab>
<Tab heading="Windows" group="win">
```shell-session
$ nomad agent -dev -config 'nomad.hcl' -network-interface '{{GetDefaultInterfaces | attr \"name\"}}'
==> Loaded configuration from nomad.hcl
==> Starting Nomad agent...
==> Nomad agent configuration:
Advertise Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
Bind Addrs: HTTP: [127.0.0.1:4646]; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
Client: true
Log Level: DEBUG
Node Id: af9bef00-2e83-b704-2d6c-c62bc005431f
Region: global (DC: dc1)
Server: true
Version: 1.7.0
==> Nomad agent started! Log data will stream in below:
...
```
</Tab>
</Tabs>
Return to your main terminal window and bootstrap the Nomad ACL system.
```shell-session
$ nomad acl bootstrap
Accessor ID = d1de8625-8556-0932-a25c-3aa71bfc0134
Secret ID = 7f10099a-936c-3f3a-8783-f0980493e54b
Name = Bootstrap Token
Type = management
Global = true
Create Time = 2023-11-16 01:09:26.565422 +0000 UTC
Expiry Time = <none>
Create Index = 23
Modify Index = 23
Policies = n/a
Roles = n/a
```
Copy the value of `Secret ID` and set it as the environment variable
`NOMAD_TOKEN`.
<Tabs>
<Tab heading="Linux" group="linux">
```shell-session
$ export NOMAD_TOKEN=...
```
</Tab>
<Tab heading="macOS" group="mac">
```shell-session
$ export NOMAD_TOKEN=...
```
</Tab>
<Tab heading="Windows" group="win">
```shell-session
$ $Env:NOMAD_TOKEN = "..."
```
</Tab>
</Tabs>
<Note title="Using Bootstrap Tokens">
The initial ACL bootstrap tokens from Vault and Nomad have full access to the
cluster and should not be used for regular day-to-day operations in a
production environment. They are used in this guide as an illustration.
We recommend creating [ACL policies][nomad_acl_policies] and
[tokens][nomad_acl_tokens] with only a level of access necessary to perform the
required operations.
</Note>
## Configure Vault to accept Nomad workload identities
### Create a Vault ACL auth method
Enable a `jwt` [auth method][vault_auth_method] in Vault under the path
`jwt-nomad`.
```shell-session
$ vault auth enable -path 'jwt-nomad' 'jwt'
Success! Enabled jwt auth method at: jwt-nomad/
```
The JWT auth method creates an endpoint that Nomad clients use to exchange
Nomad workload identity JWTs for Vault ACL tokens.
Create a file named `vault-auth-method-jwt-nomad.json`. Add the following
contents to it and save the file.
<CodeBlockConfig filename="vault-auth-method-jwt-nomad.json">
```json
{
"jwks_url": "http://127.0.0.1:4646/.well-known/jwks.json",
"jwt_supported_algs": ["RS256", "EdDSA"],
"default_role": "nomad-workloads"
}
```
</CodeBlockConfig>
This configuration file contains important information.
* `jwks_url` is the URL that Vault uses to contact Nomad and retrieve the data
necessary to validate Nomad workload identities. In a production environment,
this should resolve to multiple Nomad agents via a reverse proxy, load
balancer, or DNS entry to prevent a single point of failure.
* `default_role` is the Vault ACL role applied by default to tokens generated
by this auth method. You will create the `nomad-workloads` role in the next
section.
Apply the configuration file `vault-auth-method-jwt-nomad.json` to the
`jwt-nomad` auth method.
```shell-session
$ vault write auth/jwt-nomad/config '@vault-auth-method-jwt-nomad.json'
Success! Data written to: auth/jwt-nomad/config
```
### Create a Vault ACL role
Create a file named `vault-role-nomad-workloads.json`. Add the following
contents to it and save the file.
<CodeBlockConfig filename="vault-role-nomad-workloads.json">
```json
{
"role_type": "jwt",
"bound_audiences": ["vault.io"],
"user_claim": "/nomad_job_id",
"user_claim_json_pointer": true,
"claim_mappings": {
"nomad_namespace": "nomad_namespace",
"nomad_job_id": "nomad_job_id",
"nomad_task": "nomad_task"
},
"token_type": "service",
"token_policies": ["nomad-workloads"],
"token_period": "30m",
"token_explicit_max_ttl": 0
}
```
</CodeBlockConfig>
It defines properties for the Vault ACL tokens that are used for Nomad tasks.
* `bound_audiences` configures Vault to only accept JWTs that have an audience
value of `vault.io`. It must match the `aud` value present in the Nomad agent
configuration and jobs.
* `claim_mappings` are values from the Nomad workload identity. You will
reference them when creating the Vault ACL policy for this role.
* `token_period` determines how long the token is valid for before it expires.
Nomad automatically renews tokens before they expire.
* `token_explicit_max_ttl` is the maximum amount of time the token is valid. It
must be set to `0` so Nomad can renew them for as long as the workload runs.
* `token_policies` are the ACL policies applied to the tokens. They specify the
permissions that tokens with this role have. You will create the policy
`nomad-workloads` policy in the next section.
Create a Vault ACL role named `nomad-workloads` using the
`vault-role-nomad-workloads.json` file.
```shell-session
$ vault write auth/jwt-nomad/role/nomad-workloads '@vault-role-nomad-workloads.json'
Success! Data written to: auth/jwt-nomad/role/nomad-workloads
```
This role is applied by default to Vault ACL tokens generated from the auth
method `jwt-nomad`.
### Create a Vault ACL policy
List all of the auth methods registered in Vault.
```shell-session
$ vault auth list
Path Type Accessor Description Version
---- ---- -------- ----------- -------
jwt-nomad/ jwt auth_jwt_d34481ad n/a n/a
token/ token auth_token_510d42ca token based credentials n/a
```
Copy the `Accessor` value for the auth method in the `jwt-nomad/` path.
Create a file named `vault-policy-nomad-workloads.hcl`. Add the following
contents to it, replace all the instances of the `AUTH_METHOD_ACCESSOR`
placeholder with the `Accessor` value from the output, and save the file. There
are five occurrences to update.
<CodeBlockConfig filename="vault-policy-nomad-workloads.hcl">
```hcl
path "kv/data/{{identity.entity.aliases.AUTH_METHOD_ACCESSOR.metadata.nomad_namespace}}/{{identity.entity.aliases.AUTH_METHOD_ACCESSOR.metadata.nomad_job_id}}/*" {
capabilities = ["read"]
}
path "kv/data/{{identity.entity.aliases.AUTH_METHOD_ACCESSOR.metadata.nomad_namespace}}/{{identity.entity.aliases.AUTH_METHOD_ACCESSOR.metadata.nomad_job_id}}" {
capabilities = ["read"]
}
path "kv/metadata/{{identity.entity.aliases.AUTH_METHOD_ACCESSOR.metadata.nomad_namespace}}/*" {
capabilities = ["list"]
}
path "kv/metadata/*" {
capabilities = ["list"]
}
```
</CodeBlockConfig>
This file is a [templated Vault ACL policy][vault_tpl_policy] that
automatically grants Nomad workloads access to secrets based on the
properties mapped in the `claim_mappings` of the Vault ACL role.
More specifically, this policy grants access to secrets in the path
`kv/data/<job namespace>/<job name>/*` where `<job namespace>` and `<job name>`
are dynamically set for each workload.
Create a Vault ACL policy named `nomad-workloads` using the file
`vault-policy-nomad-workloads.hcl`.
```shell-session
$ vault policy write 'nomad-workloads' 'vault-policy-nomad-workloads.hcl'
Success! Uploaded policy: nomad-workloads
```
## Run a Nomad job to read secrets from Vault
Start a MongoDB database with a root password that is read from Vault using the
Nomad workload identity for the task.
Enable the `kv` secret engine.
```shell-session
$ vault secrets enable -version '2' 'kv'
Success! Enabled the kv secrets engine at: kv/
```
Write a secret in Vault for the database root password in the path
`default/mongo/config`.
```shell-session
$ vault kv put -mount 'kv' 'default/mongo/config' 'root_password=secret-password'
======== Secret Path ========
kv/data/default/mongo/config
======= Metadata =======
Key Value
--- -----
created_time 2023-11-21T02:52:42.061092Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
```
Create a file named `mongo.nomad.hcl`. Add the following contents to it and
save the file.
<Tabs>
<Tab heading="Linux" group="linux">
<CodeBlockConfig filename="mongo.nomad.hcl">
```hcl
job "mongo" {
namespace = "default"
group "db" {
network {
port "db" {
static = 27017
}
}
service {
provider = "nomad"
name = "mongo"
port = "db"
}
task "mongo" {
driver = "docker"
config {
image = "mongo:7"
ports = ["db"]
}
vault {}
template {
data = <<EOF
MONGO_INITDB_ROOT_USERNAME=root
MONGO_INITDB_ROOT_PASSWORD={{with secret "kv/data/default/mongo/config"}}{{.Data.data.root_password}}{{end}}
EOF
destination = "secrets/env"
env = true
}
}
}
}
```
</CodeBlockConfig>
</Tab>
<Tab heading="macOS" group="mac">
<CodeBlockConfig filename="mongo.nomad.hcl">
```hcl
job "mongo" {
namespace = "default"
group "db" {
network {
port "db" {
static = 27017
}
}
service {
provider = "nomad"
name = "mongo"
port = "db"
}
task "mongo" {
driver = "docker"
config {
image = "mongo:7"
ports = ["db"]
}
vault {}
template {
data = <<EOF
MONGO_INITDB_ROOT_USERNAME=root
MONGO_INITDB_ROOT_PASSWORD={{with secret "kv/data/default/mongo/config"}}{{.Data.data.root_password}}{{end}}
EOF
destination = "secrets/env"
env = true
}
}
}
}
```
</CodeBlockConfig>
</Tab>
<Tab heading="Windows" group="win">
<CodeBlockConfig filename="mongo.nomad.hcl">
```hcl
job "mongo" {
namespace = "default"
group "db" {
network {
port "db" {
static = 27017
}
}
service {
provider = "nomad"
name = "mongo"
port = "db"
}
task "mongo" {
driver = "docker"
config {
image = "mongo:7"
ports = ["db"]
mount {
type = "bind"
target = "C:\\mongosh"
source = "local/mongosh/mongosh-2.1.0-win32-x64/"
}
}
vault {}
template {
data = <<EOF
MONGO_INITDB_ROOT_USERNAME=root
MONGO_INITDB_ROOT_PASSWORD={{with secret "kv/data/default/mongo/config"}}{{.Data.data.root_password}}{{end}}
EOF
destination = "secrets/env"
env = true
}
artifact {
source = "https://downloads.mongodb.com/compass/mongosh-2.1.0-win32-x64.zip"
destination = "local/mongosh"
}
resources {
memory = 1000
}
}
}
}
```
</CodeBlockConfig>
</Tab>
</Tabs>
* The `vault` block indicates that the task needs access to Vault and that
Nomad should use the task workload identity to get a Vault ACL token.
* The `template` block reads the root password secret from Vault under the
path `kv/data/default/mongo/config`.
* The job runs in the Nomad `default` namespace and the job name is `mongo`.
The `nomad-workloads` Vault role grants access to secrets in the path
`kv/data/default/mongo/*`, which is where the root password exists.
* The job does not specify any identity for Vault, so Nomad uses the
`default_identity` from the agent configuration.
Run the job with the `mongo.nomad.hcl` file and wait for the deployment to
complete.
```shell-session
$ nomad job run 'mongo.nomad.hcl'
==> 2023-11-20T22:09:16-05:00: Monitoring evaluation "34e72e7a"
2023-11-20T22:09:16-05:00: Evaluation triggered by job "mongo"
2023-11-20T22:09:17-05:00: Evaluation within deployment: "919f658b"
2023-11-20T22:09:17-05:00: Allocation "49fc68d8" created: node "b8db12d2", group "db"
2023-11-20T22:09:17-05:00: Evaluation status changed: "pending" -> "complete"
==> 2023-11-20T22:09:17-05:00: Evaluation "34e72e7a" finished with status "complete"
==> 2023-11-20T22:09:17-05:00: Monitoring deployment "919f658b"
✓ Deployment "919f658b" successful
2023-11-20T22:09:28-05:00
ID = 919f658b
Job ID = mongo
Job Version = 0
Status = successful
Description = Deployment completed successfully
Deployed
Task Group Desired Placed Healthy Unhealthy Progress Deadline
db 1 1 1 0 2023-11-20T22:19:26-05:00
```
<Tabs>
<Tab heading="Linux" group="linux">
Verify that you are able to execute a query against the database using the
`root` user credentials.
```shell-session
$ nomad alloc exec "$(nomad job allocs -t '{{with (index . 0)}}{{.ID}}{{end}}' 'mongo')" mongosh --username 'root' --password 'secret-password' --eval 'db.runCommand({connectionStatus : 1})' --quiet
{
authInfo: {
authenticatedUsers: [ { user: 'root', db: 'admin' } ],
authenticatedUserRoles: [ { role: 'root', db: 'admin' } ]
},
ok: 1
}
```
</Tab>
<Tab heading="macOS" group="mac">
Verify that you are able to execute a query against the database using the
`root` user credentials.
```shell-session
$ nomad alloc exec "$(nomad job allocs -t '{{with (index . 0)}}{{.ID}}{{end}}' 'mongo')" mongosh --username 'root' --password 'secret-password' --eval 'db.runCommand({connectionStatus : 1})' --quiet
{
authInfo: {
authenticatedUsers: [ { user: 'root', db: 'admin' } ],
authenticatedUserRoles: [ { role: 'root', db: 'admin' } ]
},
ok: 1
}
```
</Tab>
<Tab heading="Windows" group="win">
Create the `root` user, reading the credentials from the environment variables
set in the job from the Vault secret.
```shell-session
$ nomad alloc exec "$(nomad job allocs -t '{{with (index . 0)}}{{.ID}}{{end}}' 'mongo')" powershell -command 'C:\mongosh\bin\mongosh.exe mongodb://localhost:27017/admin --quiet --eval ""db.createUser({user: ''$Env:MONGO_INITDB_ROOT_USERNAME'', pwd: ''$Env:MONGO_INITDB_ROOT_PASSWORD'', roles: [{role: ''root'', db: ''admin''}]})""'
{ ok: 1 }
```
Verify that you are able to execute a query against the database using the
`root` user credentials.
```shell-session
$ nomad alloc exec "$(nomad job allocs -t '{{with (index . 0)}}{{.ID}}{{end}}' 'mongo')" C:\mongosh\bin\mongosh.exe 'mongodb://localhost:27017' --username 'root' --password 'secret-password' --eval 'db.runCommand({connectionStatus : 1})' --quiet
{
authInfo: {
authenticatedUsers: [ { user: 'root', db: 'admin' } ],
authenticatedUserRoles: [ { role: 'root', db: 'admin' } ]
},
ok: 1
}
```
</Tab>
</Tabs>
Retrieve the job definition from Nomad and filter the output to only display
its task.
```shell-session
$ nomad job inspect -t '{{sprig_toPrettyJson (index (index .TaskGroups 0).Tasks 0)}}' 'mongo'
{
"Name": "mongo",
"Driver": "docker",
"User": "",
"Lifecycle": null,
"Config": {
"image": "mongo:7",
"ports": [
"db"
]
},
...
```
The `Identities` list contains the workload identity that Nomad injects
following the specification in the `default_identity` block from the Nomad
server configuration file.
<CodeBlockConfig highlight="13-26" hideClipboard>
```json
{
"Name": "mongo",
"Driver": "docker",
"User": "",
"Lifecycle": null,
"Config": {
"image": "mongo:7",
"ports": [
"db"
]
},
...
"Identities": [
{
"Name": "vault_default",
"Audience": [
"vault.io"
],
"ChangeMode": "",
"ChangeSignal": "",
"Env": false,
"File": false,
"ServiceName": "",
"TTL": 3600000000000
}
],
"Actions": null
}
```
</CodeBlockConfig>
By configuring Vault to accept workload identities from Nomad, the Nomad task
was able to automatically receive a Vault ACL token scoped to the level of
access defined by the auth method default role. With this method, Nomad agents
no longer require long-lived, highly permissive Vault tokens.
## Configure Vault dynamic secrets for MongoDB
The MongoDB database reads a secret from Vault using the permissions in the
default Vault ACL role.
In some situations, it may be necessary to customize the permissions granted to
a task to be different from this default. This is done by creating additional
Vault ACL roles for Nomad jobs.
Configure Vault with a [dynamic secret][vault_dyn] for a non-root MongoDB user
that the default Vault ACL policy does not grant access to then run a Nomad job
using a custom Vault ACL role to be able to access the dynamic secret.
Enable the Vault database secrets engine.
```shell-session
$ vault secrets enable database
Success! Enabled the database secrets engine at: database/
```
<Tabs>
<Tab heading="Linux" group="linux">
Create a file named `vault-dynamic-secret-mongo.json`. Add the following
contents to it and save the file.
<CodeBlockConfig filename="vault-dynamic-secret-mongo.json">
```json
{
"plugin_name": "mongodb-database-plugin",
"allowed_roles": "mongo",
"connection_url": "mongodb://{{username}}:{{password}}@127.0.0.1:27017/admin",
"username": "root",
"password": "secret-password"
}
```
</CodeBlockConfig>
</Tab>
<Tab heading="macOS" group="mac">
Retrieve the IP address for MongoDB from Nomad.
```shell-session
$ nomad service info mongo
Job ID Address Tags Node ID Alloc ID
mongo 192.168.0.171:27017 [] b8db12d2 49fc68d8
```
Create a file named `vault-dynamic-secret-mongo.json`. Add the following
contents to it, replace the placeholder `MONGO_IP` text with the IP address
from the Nomad `service info` command, and save the file.
<CodeBlockConfig filename="vault-dynamic-secret-mongo.json">
```json
{
"plugin_name": "mongodb-database-plugin",
"allowed_roles": "mongo",
"connection_url": "mongodb://{{username}}:{{password}}@MONGO_IP:27017/admin",
"username": "root",
"password": "secret-password"
}
```
</CodeBlockConfig>
</Tab>
<Tab heading="Windows" group="win">
Retrieve the IP address for MongoDB from Nomad.
```shell-session
$ nomad service info mongo
Job ID Address Tags Node ID Alloc ID
mongo 192.168.0.171:27017 [] b8db12d2 49fc68d8
```
Create a file named `vault-dynamic-secret-mongo.json`. Add the following
contents to it, replace the placeholder `MONGO_IP` text with the IP address
from the Nomad `service info` command, and save the file.
<CodeBlockConfig filename="vault-dynamic-secret-mongo.json">
```json
{
"plugin_name": "mongodb-database-plugin",
"allowed_roles": "mongo",
"connection_url": "mongodb://{{username}}:{{password}}@MONGO_IP:27017/admin",
"username": "root",
"password": "secret-password"
}
```
</CodeBlockConfig>
</Tab>
</Tabs>
Write the `vault-dynamic-secret-mongo.json` configuration for the MongoDB
dynamic secret to connect to the database.
```shell-session
$ vault write 'database/config/mongo' '@vault-dynamic-secret-mongo.json'
Success! Data written to: database/config/mongo
```
Create a file named `vault-database-role-mongo.json`. Add the following
contents to it and save the file.
<CodeBlockConfig filename="vault-database-role-mongo.json">
```json
{
"db_name": "mongo",
"creation_statements": "{ \"db\": \"admin\", \"roles\": [{ \"role\": \"readWrite\" }, {\"role\": \"read\", \"db\": \"foo\"}] }",
"default_ttl": "1h",
"max_ttl": "24h"
}
```
</CodeBlockConfig>
Create a Vault database role using the `vault-database-role-mongo.json` file.
```shell-session
$ vault write 'database/roles/mongo' '@vault-database-role-mongo.json'
Success! Data written to: database/roles/mongo
```
### Create a Vault ACL role to access the dynamic secret
Create a file named `vault-role-mongo-dynamic-secret.json`. Add the following
contents to it and save the file.
<CodeBlockConfig filename="vault-role-mongo-dynamic-secret.json">
```json
{
"role_type": "jwt",
"bound_audiences": ["vault.io"],
"bound_claims": {
"nomad_namespace": "default",
"nomad_job_id": "mongo-query"
},
"user_claim": "/nomad_job_id",
"user_claim_json_pointer": true,
"claim_mappings": {
"nomad_namespace": "nomad_namespace",
"nomad_job_id": "nomad_job_id",
"nomad_task": "nomad_task"
},
"token_type": "service",
"token_policies": ["mongo-dynamic-secret"],
"token_period": "30m",
"token_explicit_max_ttl": 0
}
```
</CodeBlockConfig>
This role is similar to the one from earlier in the guide but this one uses
a different policy, called `mongo-dynamic-secret`, which you will create in the
next section.
It also defines a set of `bound_claims` to restrict which workload identities
from Nomad are able to use this role. In this example, the role only allows the
job `mongo-query` in the Nomad namespace `default` to use it.
Create the `mongo-dynamic-secret` ACL role using the
`vault-role-mongo-dynamic-secret.json` file.
```shell-session
$ vault write 'auth/jwt-nomad/role/mongo-dynamic-secret' '@vault-role-mongo-dynamic-secret.json'
Success! Data written to: auth/jwt-nomad/role/mongo-dynamic-secret
```
Create a file named `vault-policy-mongo-dynamic-secret.hcl`. Add the following
contents to it and save the file.
<CodeBlockConfig filename="vault-policy-mongo-dynamic-secret.hcl">
```hcl
path "database/creds/mongo" {
capabilities = ["read"]
}
```
</CodeBlockConfig>
This ACL policy only grants access to the specific path `database/creds/mongo`,
which is not included in the default role used by the `jwt-nomad` auth method.
Create the `mongo-dynamic-secret` ACL policy using the
`vault-policy-mongo-dynamic-secret.hcl` file.
```shell-session
$ vault policy write 'mongo-dynamic-secret' 'vault-policy-mongo-dynamic-secret.hcl'
Success! Uploaded policy: mongo-dynamic-secret
```
## Run a Nomad job with a custom Vault ACL role
Create a file named `mongo-query.nomad.hcl`. Add the following contents to it
and save the file.
<Tabs>
<Tab heading="Linux" group="linux">
<CodeBlockConfig filename="mongo-query.nomad.hcl">
```hcl
job "mongo-query" {
namespace = "default"
type = "batch"
group "mongo-query" {
task "mongo-query" {
driver = "docker"
config {
image = "mongo:7"
command = "mongosh"
args = [
"--username", "${MONGO_USERNAME}",
"--password", "${MONGO_PASSWORD}",
"--eval", "db.runCommand({connectionStatus : 1})",
"--quiet",
"${MONGO_URL}",
]
}
vault {
role = "mongo-dynamic-secret"
}
template {
data = <<EOF
{{with secret "database/creds/mongo"}}
MONGO_USERNAME={{.Data.username}}
MONGO_PASSWORD={{.Data.password}}
{{end}}
{{range nomadService 1 (env "NOMAD_ALLOC_ID") "mongo"}}
MONGO_URL=mongodb://{{.Address}}:{{.Port}}
{{end}}
EOF
destination = "secrets/env"
env = true
}
}
}
}
```
</CodeBlockConfig>
</Tab>
<Tab heading="macOS" group="mac">
<CodeBlockConfig filename="mongo-query.nomad.hcl">
```hcl
job "mongo-query" {
namespace = "default"
type = "batch"
group "mongo-query" {
task "mongo-query" {
driver = "docker"
config {
image = "mongo:7"
command = "mongosh"
args = [
"--username", "${MONGO_USERNAME}",
"--password", "${MONGO_PASSWORD}",
"--eval", "db.runCommand({connectionStatus : 1})",
"--quiet",
"${MONGO_URL}",
]
}
vault {
role = "mongo-dynamic-secret"
}
template {
data = <<EOF
{{with secret "database/creds/mongo"}}
MONGO_USERNAME={{.Data.username}}
MONGO_PASSWORD={{.Data.password}}
{{end}}
{{range nomadService 1 (env "NOMAD_ALLOC_ID") "mongo"}}
MONGO_URL=mongodb://{{.Address}}:{{.Port}}
{{end}}
EOF
destination = "secrets/env"
env = true
}
}
}
}
```
</CodeBlockConfig>
</Tab>
<Tab heading="Windows" group="win">
<CodeBlockConfig filename="mongo-query.nomad.hcl">
```hcl
job "mongo-query" {
namespace = "default"
type = "batch"
group "mongo-query" {
task "mongo-query" {
driver = "docker"
config {
image = "mongo:7"
command = "C:\\mongosh\\bin\\mongosh.exe"
args = [
"--username", "${MONGO_USERNAME}",
"--password", "${MONGO_PASSWORD}",
"--eval", "db.runCommand({connectionStatus : 1})",
"--quiet",
"${MONGO_URL}",
]
mount {
type = "bind"
target = "C:\\mongosh"
source = "local/mongosh/mongosh-2.1.0-win32-x64/"
}
}
vault {
role = "mongo-dynamic-secret"
}
template {
data = <<EOF
{{with secret "database/creds/mongo"}}
MONGO_USERNAME={{.Data.username}}
MONGO_PASSWORD={{.Data.password}}
{{end}}
{{range nomadService 1 (env "NOMAD_ALLOC_ID") "mongo"}}
MONGO_URL=mongodb://{{.Address}}:{{.Port}}
{{end}}
EOF
destination = "secrets/env"
env = true
}
artifact {
source = "https://downloads.mongodb.com/compass/mongosh-2.1.0-win32-x64.zip"
destination = "local/mongosh"
}
}
}
}
```
</CodeBlockConfig>
</Tab>
</Tabs>
Note that the `vault` block specifies the `mongo-dynamic-secret` role. The task
can only access the dynamic credentials for MongoDB.
The `template` block reads these credentials and exposes them as environment
variables to the task so it can use them.
Run the Nomad job from the file `mongo-query.nomad.hcl`.
```shell-session
$ nomad job run 'mongo-query.nomad.hcl'
==> 2023-11-20T23:11:35-05:00: Monitoring evaluation "5fa56c67"
2023-11-20T23:11:35-05:00: Evaluation triggered by job "mongo-query"
2023-11-20T23:11:36-05:00: Allocation "909f0184" created: node "b8db12d2", group "mongo-query"
2023-11-20T23:11:36-05:00: Evaluation status changed: "pending" -> "complete"
==> 2023-11-20T23:11:36-05:00: Evaluation "5fa56c67" finished with status "complete"
```
Retrieve the allocation information and wait until the status is `complete`.
You may need to run the command a few times before the status changes to
`complete`.
```shell-session
$ nomad job allocs 'mongo-query'
ID Node ID Task Group Version Desired Status Created Modified
909f0184 b8db12d2 mongo-query 0 run complete 25s ago 24s ago
```
Retrieve the query result to confirm the job was able to connect to the MongoDB
database.
```shell-session
$ nomad alloc logs $(nomad job allocs -t '{{with (index . 0)}}{{.ID}}{{end}}' mongo-query)
{
authInfo: {
authenticatedUsers: [
{
user: 'v-jwt-nomad-mongo-mongo-QbF7HWHjwOi6PJWmNo1x-1700544973',
db: 'admin'
}
],
authenticatedUserRoles: [ { role: 'readWrite', db: 'admin' }, { role: 'read', db: 'foo' } ]
},
ok: 1
}
```
Note that the authenticated user is now a dynamic user credential.
Templated Vault ACL policies provide great flexibility when defining access
rules, but a single policy, or a specific group of policies, may not be enough
to cover all use cases.
Creating additional Vault ACL roles for specific Nomad jobs can help better
manage access control to Vault secrets.
## Next steps
In this guide, you configured Nomad and Vault to communicate with ACL
enabled. You also configured Nomad to automatically add workload identities for
tasks that need access to Vault.
You then deployed Nomad jobs to read static and dynamic secrets from Vault
using different Vault ACL roles and policies. Both jobs used their workload
identities to receive a Vault ACL token properly scoped to the work they
needed to do.
This process required several steps, with certain values having to match each
other in order for everything to work properly.
There are two resources available to help you automate these steps.
* The Nomad CLI command `nomad setup vault` can be useful for a quick setup
with default values for a development or test cluster.
* The [`hashicorp-modules/nomad-setup/vault`][tf_module] Terraform module
provides a basis for applying these steps with a infrastructure-as-code
approach, which is more suitable for a production environment. The
[`hashicorp-guides/nomad-workload-identity-terraform-demo`][tf_demo]
repository demonstrates how this module can be used.
You may continue exploring additional integrations between Nomad and Vault or
learn how to similarly [integrate Nomad and Consul][nomad_consul_acl] with ACL
enabled.
- [Nomad and Vault Integration][nomad_integrations_vault]
- [Nomad Workload Identity][nomad_wid]
- [Nomad Agent `vault` Configuration][nomad_config_vault]
- [Vault ACL policies tutorial][vault_tutorial_policies]
[docker_install]: https://www.docker.com/get-started/
[jwt]: https://jwt.io/
[nomad_acl_policies]: /nomad/docs/secure/acl/policies
[nomad_acl_tokens]: /nomad/docs/secure/acl/tokens
[nomad_config_vault]: /nomad/docs/configuration/vault
[nomad_consul_acl]: /nomad/tutorials/integrate-consul/consul-acl
[nomad_dev]: /nomad/commands/agent#dev
[nomad_gs]: /nomad/tutorials/get-started
[nomad_install]: /nomad/install
[nomad_integrations_vault]: /nomad/docs/secure/vault
[nomad_wid]: /nomad/docs/concepts/workload-identity
[tf_demo]: https://github.com/hashicorp-guides/nomad-workload-identity-terraform-demo
[tf_module]: https://registry.terraform.io/modules/hashicorp-modules/nomad-setup/vault
[vault_auth_method]: /vault/docs/auth
[vault_dyn]: /vault/docs/use-cases#dynamic-secrets
[vault_gs]: /vault/tutorials/get-started
[vault_install]: /vault/install
[vault_static]: /vault/docs/use-cases#static-secrets
[vault_tpl_policy]: /vault/docs/concepts/policies#templated-policies
[vault_tutorial_policies]: /vault/tutorials/policies