Skip to content

Commit

Permalink
Merge pull request #14088 from hashicorp/b-plan-vault-token
Browse files Browse the repository at this point in the history
cli: support vault token in plan command
  • Loading branch information
shoenig committed Aug 12, 2022
2 parents 9504a45 + e96d52d commit d3222e2
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/14088.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
cli: Fixed a bug where vault token not respected in plan command
```
43 changes: 43 additions & 0 deletions command/job_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package command

import (
"fmt"
"os"
"sort"
"strings"
"time"

"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/helper/pointer"
"github.com/hashicorp/nomad/scheduler"
"github.com/posener/complete"
)
Expand Down Expand Up @@ -62,6 +64,10 @@ Alias: nomad plan
* 1: Allocations created or destroyed.
* 255: Error determining plan results.
The plan command will set the vault_token of the job based on the following
precedence, going from highest to lowest: the -vault-token flag, the
$VAULT_TOKEN environment variable and finally the value in the job file.
When ACLs are enabled, this command requires a token with the 'submit-job'
capability for the job's namespace.
Expand Down Expand Up @@ -91,6 +97,22 @@ Plan Options:
-policy-override
Sets the flag to force override any soft mandatory Sentinel policies.
-vault-token
Used to validate if the user submitting the job has permission to run the job
according to its Vault policies. A Vault token must be supplied if the vault
stanza allow_unauthenticated is disabled in the Nomad server configuration.
If the -vault-token flag is set, the passed Vault token is added to the jobspec
before sending to the Nomad servers. This allows passing the Vault token
without storing it in the job file. This overrides the token found in the
$VAULT_TOKEN environment variable and the vault_token field in the job file.
This token is cleared from the job after validating and cannot be used within
the job executing environment. Use the vault stanza when templating in a job
with a Vault token.
-vault-namespace
If set, the passed Vault namespace is stored in the job before sending to the
Nomad servers.
-var 'key=value'
Variable for template, can be used multiple times.
Expand All @@ -116,6 +138,8 @@ func (c *JobPlanCommand) AutocompleteFlags() complete.Flags {
"-json": complete.PredictNothing,
"-hcl1": complete.PredictNothing,
"-hcl2-strict": complete.PredictNothing,
"-vault-token": complete.PredictAnything,
"-vault-namespace": complete.PredictAnything,
"-var": complete.PredictAnything,
"-var-file": complete.PredictFiles("*.var"),
})
Expand All @@ -132,6 +156,7 @@ func (c *JobPlanCommand) AutocompleteArgs() complete.Predictor {
func (c *JobPlanCommand) Name() string { return "job plan" }
func (c *JobPlanCommand) Run(args []string) int {
var diff, policyOverride, verbose bool
var vaultToken, vaultNamespace string

flagSet := c.Meta.FlagSet(c.Name(), FlagSetClient)
flagSet.Usage = func() { c.Ui.Output(c.Help()) }
Expand All @@ -141,6 +166,8 @@ func (c *JobPlanCommand) Run(args []string) int {
flagSet.BoolVar(&c.JobGetter.JSON, "json", false, "")
flagSet.BoolVar(&c.JobGetter.HCL1, "hcl1", false, "")
flagSet.BoolVar(&c.JobGetter.Strict, "hcl2-strict", true, "")
flagSet.StringVar(&vaultToken, "vault-token", "", "")
flagSet.StringVar(&vaultNamespace, "vault-namespace", "", "")
flagSet.Var(&c.JobGetter.Vars, "var", "")
flagSet.Var(&c.JobGetter.VarFiles, "var-file", "")

Expand Down Expand Up @@ -186,6 +213,22 @@ func (c *JobPlanCommand) Run(args []string) int {
client.SetNamespace(*n)
}

// Parse the Vault token.
if vaultToken == "" {
// Check the environment variable
vaultToken = os.Getenv("VAULT_TOKEN")
}

// Set the vault token.
if vaultToken != "" {
job.VaultToken = pointer.Of(vaultToken)
}

// Set the vault namespace.
if vaultNamespace != "" {
job.VaultNamespace = pointer.Of(vaultNamespace)
}

// Setup the options
opts := &api.PlanOptions{}
if diff {
Expand Down
55 changes: 55 additions & 0 deletions command/job_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/testutil"
"github.com/mitchellh/cli"
"github.com/shoenig/test/must"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -156,6 +157,60 @@ job "job1" {
ui.ErrorWriter.Reset()
}

func TestPlanCommand_From_Files(t *testing.T) {
ci.Parallel(t)

// Create a Vault server
v := testutil.NewTestVault(t)
defer v.Stop()

// Create a Nomad server
s := testutil.NewTestServer(t, func(c *testutil.TestServerConfig) {
c.Vault.Address = v.HTTPAddr
c.Vault.Enabled = true
c.Vault.AllowUnauthenticated = false
c.Vault.Token = v.RootToken
})
defer s.Stop()

t.Run("fail to place", func(t *testing.T) {
ui := cli.NewMockUi()
cmd := &JobPlanCommand{Meta: Meta{Ui: ui}}
args := []string{"-address", "http://" + s.HTTPAddr, "testdata/example-basic.nomad"}
code := cmd.Run(args)
require.Equal(t, 1, code) // no client running, fail to place
must.StrContains(t, ui.OutputWriter.String(), "WARNING: Failed to place all allocations.")
})

t.Run("vault no token", func(t *testing.T) {
ui := cli.NewMockUi()
cmd := &JobPlanCommand{Meta: Meta{Ui: ui}}
args := []string{"-address", "http://" + s.HTTPAddr, "testdata/example-vault.nomad"}
code := cmd.Run(args)
must.Eq(t, 255, code)
must.StrContains(t, ui.ErrorWriter.String(), "* Vault used in the job but missing Vault token")
})

t.Run("vault bad token via flag", func(t *testing.T) {
ui := cli.NewMockUi()
cmd := &JobPlanCommand{Meta: Meta{Ui: ui}}
args := []string{"-address", "http://" + s.HTTPAddr, "-vault-token=abc123", "testdata/example-vault.nomad"}
code := cmd.Run(args)
must.Eq(t, 255, code)
must.StrContains(t, ui.ErrorWriter.String(), "* bad token")
})

t.Run("vault bad token via env", func(t *testing.T) {
t.Setenv("VAULT_TOKEN", "abc123")
ui := cli.NewMockUi()
cmd := &JobPlanCommand{Meta: Meta{Ui: ui}}
args := []string{"-address", "http://" + s.HTTPAddr, "testdata/example-vault.nomad"}
code := cmd.Run(args)
must.Eq(t, 255, code)
must.StrContains(t, ui.ErrorWriter.String(), "* bad token")
})
}

func TestPlanCommand_From_URL(t *testing.T) {
ci.Parallel(t)
ui := cli.NewMockUi()
Expand Down
20 changes: 20 additions & 0 deletions website/content/docs/commands/job/plan.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ Plan will return one of the following exit codes:
- 1: Allocations created or destroyed.
- 255: Error determining plan results.

The plan command will set the `vault_token` of the job based on the following
precedence, going from highest to lowest: the `-vault-token` flag, the
`$VAULT_TOKEN` environment variable and finally the value in the job file.

When ACLs are enabled, this command requires a token with the `submit-job`
capability for the job's namespace.

Expand All @@ -73,6 +77,20 @@ capability for the job's namespace.
a variable has been supplied which is not defined within the root variables.
Defaults to true.

- `-vault-token`: Used to validate if the user submitting the job has
permission to run the job according to its Vault policies. A Vault token must
be supplied if the [`vault` stanza `allow_unauthenticated`] is disabled in
the Nomad server configuration. If the `-vault-token` flag is set, the passed
Vault token is added to the jobspec before sending to the Nomad servers. This
allows passing the Vault token without storing it in the job file. This
overrides the token found in the `$VAULT_TOKEN` environment variable and the
[`vault_token`] field in the job file. This token is cleared from the job
after planning and cannot be used within the job executing environment. Use
the `vault` stanza when templating in a job with a Vault token.

- `-vault-namespace`: If set, the passed Vault namespace is stored in the job
before sending to the Nomad servers.

- `-var=<key=value>`: Variable for template, can be used multiple times.

- `-var-file=<path>`: Path to HCL2 file containing user variables.
Expand Down Expand Up @@ -241,3 +259,5 @@ if a change is detected.
[`go-getter`]: https://github.com/hashicorp/go-getter
[`nomad job run -check-index`]: /docs/commands/job/run#check-index
[`tee`]: https://man7.org/linux/man-pages/man1/tee.1.html
[`vault` stanza `allow_unauthenticated`]: /docs/configuration/vault#allow_unauthenticated
[`vault_token`]: /docs/job-specification/job#vault_token

0 comments on commit d3222e2

Please sign in to comment.