github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/website/pages/docs/integrations/vault-integration.mdx (about)

     1  ---
     2  layout: docs
     3  page_title: Vault Integration and Retrieving Dynamic Secrets
     4  sidebar_title: Vault
     5  description: |-
     6    Learn how to deploy an application in Nomad and retrieve dynamic credentials
     7    by integrating with Vault.
     8  ---
     9  
    10  # Vault Integration
    11  
    12  Nomad integrates seamlessly with [Vault][vault] and allows your application to
    13  retrieve dynamic credentials for various tasks. In this guide, you will deploy a
    14  web application that needs to authenticate against [PostgreSQL][postgresql] to
    15  display data from a table to the user.
    16  
    17  ## Reference Material
    18  
    19  - [Vault Integration Documentation][vault-integration]
    20  - [Nomad Template Stanza Integration with Vault][nomad-template-vault]
    21  - [Secrets Task Directory][secrets-task-directory]
    22  
    23  ## Estimated Time to Complete
    24  
    25  20 minutes
    26  
    27  ## Challenge
    28  
    29  Think of a scenario where a Nomad operator needs to deploy an application that
    30  can quickly and safely retrieve dynamic credentials to authenticate against a
    31  database and return information.
    32  
    33  ## Solution
    34  
    35  Deploy Vault and configure the nodes in your Nomad cluster to integrate with it.
    36  Use the appropriate [templating syntax][nomad-template-vault] to retrieve
    37  credentials from Vault and then store those credentials in the
    38  [secrets][secrets-task-directory] task directory to be consumed by the Nomad
    39  task.
    40  
    41  ## Prerequisites
    42  
    43  To perform the tasks described in this guide, you need to have a Nomad
    44  environment with Consul and Vault installed. You can use this [repo][repo] to
    45  easily provision a sandbox environment. This guide will assume a cluster with
    46  one server node and three client nodes.
    47  
    48  -> **Please Note:** This guide is for demo purposes and is only using a single
    49  Nomad server with Vault installed alongside. In a production cluster, 3 or 5
    50  Nomad server nodes are recommended along with a separate Vault cluster.
    51  
    52  ## Steps
    53  
    54  ### Step 1: Initialize Vault Server
    55  
    56  Run the following command to initialize Vault server and receive an
    57  [unseal][seal] key and initial root [token][token]. Be sure to note the unseal
    58  key and initial root token as you will need these two pieces of information.
    59  
    60  ```shell-sessionvault operator init -key-shares=1 -key-threshold=1
    61  
    62  ```
    63  
    64  The `vault operator init` command above creates a single Vault unseal key for
    65  convenience. For a production environment, it is recommended that you create at
    66  least five unseal key shares and securely distribute them to independent
    67  operators. The `vault operator init` command defaults to five key shares and a
    68  key threshold of three. If you provisioned more than one server, the others will
    69  become standby nodes but should still be unsealed.
    70  
    71  ### Step 2: Unseal Vault
    72  
    73  Run the following command and then provide your unseal key to Vault.
    74  
    75  ```shell-sessionvault operator unseal
    76  
    77  ```
    78  
    79  The output of unsealing Vault will look similar to the following:
    80  
    81  ```shell
    82  Key                    Value
    83  ---                    -----
    84  Seal Type              shamir
    85  Initialized            true
    86  Sealed                 false
    87  Total Shares           1
    88  Threshold              1
    89  Version                0.11.4
    90  Cluster Name           vault-cluster-d12535e5
    91  Cluster ID             49383931-c782-fdc6-443e-7681e7b15aca
    92  HA Enabled             true
    93  HA Cluster             n/a
    94  HA Mode                standby
    95  Active Node Address    <none>
    96  ```
    97  
    98  ### Step 3: Log in to Vault
    99  
   100  Use the [login][login] command to authenticate yourself against Vault using the
   101  initial root token you received earlier. You will need to authenticate to run
   102  the necessary commands to write policies, create roles, and configure a
   103  connection to your database.
   104  
   105  ```shell-sessionvault login <your initial root token>
   106  
   107  ```
   108  
   109  If your login is successful, you will see output similar to what is shown below:
   110  
   111  ```shell
   112  Success! You are now authenticated. The token information displayed below
   113  is already stored in the token helper. You do NOT need to run "vault login"
   114  again. Future Vault requests will automatically use this token.
   115  ...
   116  ```
   117  
   118  ### Step 4: Write the Policy for the Nomad Server Token
   119  
   120  To use the Vault integration, you must provide a Vault token to your Nomad
   121  servers. Although you can provide your root token to easily get started, the
   122  recommended approach is to use a token [role][role] based token. This first
   123  requires writing a policy that you will attach to the token you provide to your
   124  Nomad servers. By using this approach, you can limit the set of
   125  [policies][policy] that tasks managed by Nomad can access.
   126  
   127  For this exercise, use the following policy for the token you will create for
   128  your Nomad server. Place this policy in a file named `nomad-server-policy.hcl`.
   129  
   130  ```hcl
   131  # Allow creating tokens under "nomad-cluster" token role. The token role name
   132  # should be updated if "nomad-cluster" is not used.
   133  path "auth/token/create/nomad-cluster" {
   134    capabilities = ["update"]
   135  }
   136  
   137  # Allow looking up "nomad-cluster" token role. The token role name should be
   138  # updated if "nomad-cluster" is not used.
   139  path "auth/token/roles/nomad-cluster" {
   140    capabilities = ["read"]
   141  }
   142  
   143  # Allow looking up the token passed to Nomad to validate # the token has the
   144  # proper capabilities. This is provided by the "default" policy.
   145  path "auth/token/lookup-self" {
   146    capabilities = ["read"]
   147  }
   148  
   149  # Allow looking up incoming tokens to validate they have permissions to access
   150  # the tokens they are requesting. This is only required if
   151  # `allow_unauthenticated` is set to false.
   152  path "auth/token/lookup" {
   153    capabilities = ["update"]
   154  }
   155  
   156  # Allow revoking tokens that should no longer exist. This allows revoking
   157  # tokens for dead tasks.
   158  path "auth/token/revoke-accessor" {
   159    capabilities = ["update"]
   160  }
   161  
   162  # Allow checking the capabilities of our own token. This is used to validate the
   163  # token upon startup.
   164  path "sys/capabilities-self" {
   165    capabilities = ["update"]
   166  }
   167  
   168  # Allow our own token to be renewed.
   169  path "auth/token/renew-self" {
   170    capabilities = ["update"]
   171  }
   172  ```
   173  
   174  You can now write a policy called `nomad-server` by running the following
   175  command:
   176  
   177  ```shell-sessionvault policy write nomad-server nomad-server-policy.hcl
   178  
   179  ```
   180  
   181  You should see the following output:
   182  
   183  ```shell
   184  Success! Uploaded policy: nomad-server
   185  ```
   186  
   187  You will generate the actual token in the next few steps.
   188  
   189  ### Step 5: Create a Token Role
   190  
   191  At this point, you must create a Vault token role that Nomad can use. The token
   192  role allows you to limit what Vault policies are accessible by jobs
   193  submitted to Nomad. We will use the following token role:
   194  
   195  ```json
   196  {
   197    "allowed_policies": "access-tables",
   198    "token_explicit_max_ttl": 0,
   199    "name": "nomad-cluster",
   200    "orphan": true,
   201    "token_period": 259200,
   202    "renewable": true
   203  }
   204  ```
   205  
   206  Please notice that the `access-tables` policy is listed under the
   207  `allowed_policies` key. We have not created this policy yet, but it will be used
   208  by our job to retrieve credentials to access the database. A job running in our
   209  Nomad cluster will only be allowed to use the `access-tables` policy.
   210  
   211  If you would like to allow all policies to be used by any job in the Nomad
   212  cluster except for the ones you specifically prohibit, then use the
   213  `disallowed_policies` key instead and simply list the policies that should not
   214  be granted. If you take this approach, be sure to include `nomad-server` in the
   215  disallowed policies group. An example of this is shown below:
   216  
   217  ```json
   218  {
   219    "disallowed_policies": "nomad-server",
   220    "token_explicit_max_ttl": 0,
   221    "name": "nomad-cluster",
   222    "orphan": true,
   223    "token_period": 259200,
   224    "renewable": true
   225  }
   226  ```
   227  
   228  Save the policy in a file named `nomad-cluster-role.json` and create the token
   229  role named `nomad-cluster`.
   230  
   231  ```shell-sessionvault write /auth/token/roles/nomad-cluster @nomad-cluster-role.json
   232  
   233  ```
   234  
   235  You should see the following output:
   236  
   237  ```shell
   238  Success! Data written to: auth/token/roles/nomad-cluster
   239  ```
   240  
   241  ### Step 6: Generate the Token for the Nomad Server
   242  
   243  Run the following command to create a token for your Nomad server:
   244  
   245  ```shell-sessionvault token create -policy nomad-server -period 72h -orphan
   246  
   247  ```
   248  
   249  The `-orphan` flag is included when generating the Nomad server token above to
   250  prevent revocation of the token when its parent expires. Vault typically creates
   251  tokens with a parent-child relationship. When an ancestor token is revoked, all
   252  of its descendant tokens and their associated leases are revoked as well.
   253  
   254  If everything works, you should see output similar to the following:
   255  
   256  ```shell
   257  Key                  Value
   258  ---                  -----
   259  token                1gr0YoLyTBVZl5UqqvCfK9RJ
   260  token_accessor       5fz20DuDbxKgweJZt3cMynya
   261  token_duration       72h
   262  token_renewable      true
   263  token_policies       ["default" "nomad-server"]
   264  identity_policies    []
   265  policies             ["default" "nomad-server"]
   266  ```
   267  
   268  ### Step 7: Edit the Nomad Server Configuration to Enable Vault Integration
   269  
   270  At this point, you are ready to edit the [vault stanza][vault-stanza] in the
   271  Nomad Server's configuration file located at `/etc/nomad.d/nomad.hcl`. Provide
   272  the token you generated in the previous step in the `vault` stanza of your Nomad
   273  server configuration. The token can also be provided as an environment variable
   274  called `VAULT_TOKEN`. Be sure to specify the `nomad-cluster-role` in the
   275  [create_from_role][create-from-role] option. If using
   276  [Vault Namespaces](https://www.vaultproject.io/docs/enterprise/namespaces),
   277  modify both the client and server configuration to include the namespace;
   278  alternatively, it can be provided in the environment variable `VAULT_NAMESPACE`.
   279  After following these steps and enabling Vault, the `vault` stanza in your Nomad
   280  server configuration will be similar to what is shown below:
   281  
   282  ```hcl
   283  vault {
   284    enabled = true
   285    address = "http://active.vault.service.consul:8200"
   286    task_token_ttl = "1h"
   287    create_from_role = "nomad-cluster"
   288    token = "<your nomad server token>"
   289    namespace = "<vault namespace for the cluster>"
   290  }
   291  ```
   292  
   293  Restart the Nomad server
   294  
   295  ```shell-sessionsudo systemctl restart nomad
   296  
   297  ```
   298  
   299  NOTE: Nomad servers will renew the token automatically.
   300  
   301  Vault integration needs to be enabled on the client nodes as well, but this has
   302  been configured for you already in this environment. You will see the `vault`
   303  stanza in your Nomad clients' configuration (located at
   304  `/etc/nomad.d/nomad.hcl`) looks similar to the following:
   305  
   306  ```hcl
   307  vault {
   308    enabled = true
   309    address = "http://active.vault.service.consul:8200"
   310  }
   311  ```
   312  
   313  Please note that the Nomad clients do not need to be provided with a Vault
   314  token.
   315  
   316  ### Step 8: Deploy Database
   317  
   318  The next few steps will involve configuring a connection between Vault and our
   319  database, so let's deploy one that we can connect to. Create a Nomad job called
   320  `db.nomad` with the following content:
   321  
   322  ```hcl
   323  job "postgres-nomad-demo" {
   324    datacenters = ["dc1"]
   325  
   326    group "db" {
   327  
   328      task "server" {
   329        driver = "docker"
   330  
   331        config {
   332          image = "hashicorp/postgres-nomad-demo:latest"
   333          port_map {
   334            db = 5432
   335          }
   336        }
   337        resources {
   338          network {
   339            port  "db"{
   340  	    static = 5432
   341  	  }
   342          }
   343        }
   344  
   345        service {
   346          name = "database"
   347          port = "db"
   348  
   349          check {
   350            type     = "tcp"
   351            interval = "2s"
   352            timeout  = "2s"
   353          }
   354        }
   355      }
   356    }
   357  }
   358  ```
   359  
   360  Run the job as shown below:
   361  
   362  ```shell-sessionnomad run db.nomad
   363  
   364  ```
   365  
   366  Verify the job is running with the following command:
   367  
   368  ```shell-sessionnomad status postgres-nomad-demo
   369  
   370  ```
   371  
   372  The result of the status command will look similar to the output below:
   373  
   374  ```shell
   375  ID            = postgres-nomad-demo
   376  Name          = postgres-nomad-demo
   377  Submit Date   = 2018-11-15T21:01:00Z
   378  Type          = service
   379  Priority      = 50
   380  Datacenters   = dc1
   381  Status        = running
   382  Periodic      = false
   383  Parameterized = false
   384  
   385  Summary
   386  Task Group  Queued  Starting  Running  Failed  Complete  Lost
   387  db          0       0         1        0       0         0
   388  
   389  Allocations
   390  ID        Node ID   Task Group  Version  Desired  Status   Created    Modified
   391  701e2699  5de1330c  db          0        run      running  1m56s ago  1m33s ago
   392  ```
   393  
   394  Now we can move on to configuring the connection between Vault and our database.
   395  
   396  ### Step 9: Enable the Database Secrets Engine
   397  
   398  We are using the database secrets engine for Vault in this exercise so that we
   399  can generate dynamic credentials for our PostgreSQL database. Run the following command to enable it:
   400  
   401  ```shell-sessionvault secrets enable database
   402  
   403  ```
   404  
   405  If the previous command was successful, you will see the following output:
   406  
   407  ```shell
   408  Success! Enabled the database secrets engine at: database/
   409  ```
   410  
   411  ### Step 10: Configure the Database Secrets Engine
   412  
   413  Create a file named `connection.json` and placed the following information into
   414  it:
   415  
   416  ```json
   417  {
   418    "plugin_name": "postgresql-database-plugin",
   419    "allowed_roles": "accessdb",
   420    "connection_url": "postgresql://{{username}}:{{password}}@database.service.consul:5432/postgres?sslmode=disable",
   421    "username": "postgres",
   422    "password": "postgres123"
   423  }
   424  ```
   425  
   426  The information above allows Vault to connect to our database and create users
   427  with specific privileges. We will specify the `accessdb` role soon. In a
   428  production setting, it is recommended to give Vault credentials with enough
   429  privileges to generate database credentials dynamically and and manage their
   430  lifecycle.
   431  
   432  Run the following command to configure the connection between the database
   433  secrets engine and our database:
   434  
   435  ```shell-sessionvault write database/config/postgresql @connection.json
   436  
   437  ```
   438  
   439  If the operation is successful, there will be no output.
   440  
   441  ### Step 11: Create a Vault Role to Manage Database Privileges
   442  
   443  Recall from the previous step that we specified `accessdb` in the
   444  `allowed_roles` key of our connection information. Let's set up that role now. Create a file called `accessdb.sql` with the following content:
   445  
   446  ```shell
   447  CREATE USER "{{name}}" WITH ENCRYPTED PASSWORD '{{password}}' VALID UNTIL
   448  '{{expiration}}';
   449  GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO "{{name}}";
   450  GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}";
   451  GRANT ALL ON SCHEMA public TO "{{name}}";
   452  ```
   453  
   454  The SQL above will be used in the [creation_statements][creation-statements]
   455  parameter of our next command to specify the privileges that the dynamic
   456  credentials being generated will possess. In our case, the dynamic database user
   457  will have broad privileges that include the ability to read from the tables that
   458  our application will need to access.
   459  
   460  Run the following command to create the role:
   461  
   462  ```shell-sessionvault write database/roles/accessdb db_name=postgresql \
   463  creation_statements=@accessdb.sql default_ttl=1h max_ttl=24h
   464  ```
   465  
   466  You should see the following output after running the previous command:
   467  
   468  ```shell
   469  Success! Data written to: database/roles/accessdb
   470  ```
   471  
   472  ### Step 12: Generate PostgreSQL Credentials
   473  
   474  You should now be able to generate dynamic credentials to access your database.
   475  Run the following command to generate a set of credentials:
   476  
   477  ```shell-sessionvault read database/creds/accessdb
   478  
   479  ```
   480  
   481  The previous command should return output similar to what is shown below:
   482  
   483  ```shell
   484  Key                Value
   485  ---                -----
   486  lease_id           database/creds/accessdb/3JozEMSMqw0vHHhvla15sKTW
   487  lease_duration     1h
   488  lease_renewable    true
   489  password           A1a-3pMGjpDXHZ2Qzuf7
   490  username           v-root-accessdb-5LA65urB4daA8KYy2xku-1542318363
   491  ```
   492  
   493  Congratulations! You have configured Vault's connection to your database and can
   494  now generate credentials with the previously specified privileges. Now we need
   495  to deploy our application and make sure that it will be able to communicate with
   496  Vault and obtain the credentials as well.
   497  
   498  ### Step 13: Create the `access-tables` Policy for Your Nomad Job to Use
   499  
   500  Recall from [Step 5][step-5] that we specified a policy named `access-tables` in
   501  our `allowed_policies` section of the token role. We will create this policy now
   502  and give it the capability to read from the `database/creds/accessdb` endpoint
   503  (the same endpoint we read from in the previous step to generate credentials for
   504  our database). We will then specify this policy in our Nomad job which will
   505  allow it to retrieve credentials for itself to access the database.
   506  
   507  On the Nomad server (which is also running Vault), create a file named
   508  `access-tables-policy.hcl` with the following content:
   509  
   510  ```hcl
   511  path "database/creds/accessdb" {
   512    capabilities = ["read"]
   513  }
   514  ```
   515  
   516  Create the `access-tables` policy with the following command:
   517  
   518  ```shell-sessionvault policy write access-tables access-tables-policy.hcl
   519  
   520  ```
   521  
   522  You should see the following output:
   523  
   524  ```shell
   525  Success! Uploaded policy: access-tables
   526  ```
   527  
   528  ### Step 14: Deploy Your Job with the Appropriate Policy and Templating
   529  
   530  Now we are ready to deploy our web application and give it the necessary policy
   531  and configuration to communicate with our database. Create a file called
   532  `web-app.nomad` and save the following content in it.
   533  
   534  ```hcl
   535  job "nomad-vault-demo" {
   536    datacenters = ["dc1"]
   537  
   538    group "demo" {
   539      task "server" {
   540  
   541        vault {
   542          policies = ["access-tables"]
   543        }
   544  
   545        driver = "docker"
   546        config {
   547          image = "hashicorp/nomad-vault-demo:latest"
   548          port_map {
   549            http = 8080
   550          }
   551  
   552          volumes = [
   553            "secrets/config.json:/etc/demo/config.json"
   554          ]
   555        }
   556  
   557        template {
   558          data = <<EOF
   559  {{ with secret "database/creds/accessdb" }}
   560    {
   561      "host": "database.service.consul",
   562      "port": 5432,
   563      "username": "{{ .Data.username }}",
   564      {{ /* Ensure password is a properly escaped JSON string. */ }}
   565      "password": {{ .Data.password | toJSON }},
   566      "db": "postgres"
   567    }
   568  {{ end }}
   569  EOF
   570          destination = "secrets/config.json"
   571        }
   572  
   573        resources {
   574          network {
   575            port "http" {}
   576          }
   577        }
   578  
   579        service {
   580          name = "nomad-vault-demo"
   581          port = "http"
   582  
   583          tags = [
   584            "urlprefix-/",
   585          ]
   586  
   587          check {
   588            type     = "tcp"
   589            interval = "2s"
   590            timeout  = "2s"
   591          }
   592        }
   593      }
   594    }
   595  }
   596  ```
   597  
   598  There are a few key points to note here:
   599  
   600  - We have specified the `access-tables` policy in the [vault][vault-jobspec]
   601    stanza of this job. The Nomad client will receive a token with this policy
   602    attached. Recall from the previous step that this policy will allow our
   603    application to read from the `database/creds/accessdb` endpoint in Vault and
   604    retrieve credentials.
   605  - We are using the [template][template] stanza's [vault
   606    integration][nomad-template-vault] to populate the JSON configuration file
   607    that our application needs. The underlying tool being used is [Consul
   608    Template][consul-template]. You can use Consul Template's documentation to
   609    learn more about the [syntax][consul-temp-syntax] needed to interact with
   610    Vault. Please note that although we have defined the template
   611    [inline][inline], we can use the template stanza [in conjunction with the
   612    artifact stanza][remote-template] to download an input template from a remote
   613    source such as an S3 bucket.
   614  - We are using the `toJSON` function to ensure the password is encoded as a JSON
   615    string. Any templated value which may contain special characters (like quotes
   616    or newlines) should be passed through the `toJSON` function.
   617  - Finally, note that that [destination][destination] of our template is the
   618    [secrets/][secrets-task-directory] task directory. This ensures the data is
   619    not accessible with a command like [nomad alloc fs][nomad-alloc-fs] or
   620    filesystem APIs.
   621  
   622  Use the following command to run the job:
   623  
   624  ```shell-sessionnomad run web-app.nomad
   625  
   626  ```
   627  
   628  ### Step 15: Confirm the Application is Accessing the Database
   629  
   630  At this point, you can visit your application at the path `/names` to confirm
   631  the appropriate data is being accessed from the database and displayed to you.
   632  There are several ways to do this.
   633  
   634  - Use the `dig` command to query the SRV record of your service and obtain the
   635    port it is using. Then `curl` your service at the appropriate port and `names` path.
   636  
   637  ```shell-sessiondig +short SRV nomad-vault-demo.service.consul
   638  1 1 30478 ip-172-31-58-230.node.dc1.consul.
   639  ```
   640  
   641  ```shell-sessioncurl nomad-vault-demo.service.consul:30478/names
   642  <!DOCTYPE html>
   643  <html>
   644  <body>
   645  
   646  <h1> Welcome! </h1>
   647  <h2> If everything worked correctly, you should be able to see a list of names
   648  below </h2>
   649  
   650  <hr>
   651  
   652  
   653  <h4> John Doe </h4>
   654  
   655  <h4> Peter Parker </h4>
   656  
   657  <h4> Clifford Roosevelt </h4>
   658  
   659  <h4> Bruce Wayne </h4>
   660  
   661  <h4> Steven Clark </h4>
   662  
   663  <h4> Mary Jane </h4>
   664  
   665  
   666  </body>
   667  <html>
   668  ```
   669  
   670  - You can also deploy [fabio][fabio] and visit any Nomad client at its public IP
   671    address using a fixed port. The details of this method are beyond the scope of
   672    this guide, but you can refer to the [Load Balancing with Fabio][fabio-lb]
   673    guide for more information on this topic. Alternatively, you could use the
   674    `nomad` [alloc status][alloc-status] command along with the AWS console to
   675    determine the public IP and port your service is running (remember to open the
   676    port in your AWS security group if you choose this method).
   677  
   678  [![Web Service][web-service]][web-service]
   679  
   680  [alloc-status]: /docs/commands/alloc/status
   681  [consul-template]: https://github.com/hashicorp/consul-template
   682  [consul-temp-syntax]: https://github.com/hashicorp/consul-template#secret
   683  [create-from-role]: /docs/configuration/vault#create_from_role
   684  [creation-statements]: https://www.vaultproject.io/api/secret/databases#creation_statements
   685  [destination]: /docs/job-specification/template#destination
   686  [fabio]: https://github.com/fabiolb/fabio
   687  [fabio-lb]: https://learn.hashicorp.com/nomad/load-balancing/fabio
   688  [inline]: /docs/job-specification/template#inline-template
   689  [login]: https://www.vaultproject.io/docs/commands/login
   690  [nomad-alloc-fs]: /docs/commands/alloc/fs
   691  [nomad-template-vault]: /docs/job-specification/template#vault-integration
   692  [policy]: https://www.vaultproject.io/docs/concepts/policies
   693  [postgresql]: https://www.postgresql.org/about/
   694  [remote-template]: /docs/job-specification/template#remote-template
   695  [repo]: https://github.com/hashicorp/nomad/tree/master/terraform
   696  [role]: https://www.vaultproject.io/docs/auth/token
   697  [seal]: https://www.vaultproject.io/docs/concepts/seal
   698  [secrets-task-directory]: /docs/runtime/environment#secrets
   699  [step-5]: /docs/integrations/vault-integration#step-5-create-a-token-role
   700  [template]: /docs/job-specification/template
   701  [token]: https://www.vaultproject.io/docs/concepts/tokens
   702  [vault]: https://www.vaultproject.io/
   703  [vault-integration]: /docs/vault-integration
   704  [vault-jobspec]: /docs/job-specification/vault
   705  [vault-stanza]: /docs/configuration/vault
   706  [web-service]: /img/nomad-demo-app.png