cli: respect vault token in plan command

This PR fixes a regression where the 'job plan' command would not respect
a Vault token if set via --vault-token or $VAULT_TOKEN.

Basically the same bug/fix as for the validate command in https://github.com/hashicorp/nomad/issues/13062

Fixes https://github.com/hashicorp/nomad/issues/13939
This commit is contained in:
Seth Hoenig 2022-08-11 08:51:45 -05:00
parent 1901cfaba8
commit ba5c45ab93
4 changed files with 121 additions and 0 deletions

3
.changelog/14088.txt Normal file
View file

@ -0,0 +1,3 @@
```release-note:bug
cli: Fixed a bug where vault token not respected in plan command
```

View file

@ -2,11 +2,13 @@ package command
import ( import (
"fmt" "fmt"
"os"
"sort" "sort"
"strings" "strings"
"time" "time"
"github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/helper/pointer"
"github.com/hashicorp/nomad/scheduler" "github.com/hashicorp/nomad/scheduler"
"github.com/posener/complete" "github.com/posener/complete"
) )
@ -62,6 +64,10 @@ Alias: nomad plan
* 1: Allocations created or destroyed. * 1: Allocations created or destroyed.
* 255: Error determining plan results. * 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' When ACLs are enabled, this command requires a token with the 'submit-job'
capability for the job's namespace. capability for the job's namespace.
@ -91,6 +97,22 @@ Plan Options:
-policy-override -policy-override
Sets the flag to force override any soft mandatory Sentinel policies. 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' -var 'key=value'
Variable for template, can be used multiple times. Variable for template, can be used multiple times.
@ -116,6 +138,8 @@ func (c *JobPlanCommand) AutocompleteFlags() complete.Flags {
"-json": complete.PredictNothing, "-json": complete.PredictNothing,
"-hcl1": complete.PredictNothing, "-hcl1": complete.PredictNothing,
"-hcl2-strict": complete.PredictNothing, "-hcl2-strict": complete.PredictNothing,
"-vault-token": complete.PredictAnything,
"-vault-namespace": complete.PredictAnything,
"-var": complete.PredictAnything, "-var": complete.PredictAnything,
"-var-file": complete.PredictFiles("*.var"), "-var-file": complete.PredictFiles("*.var"),
}) })
@ -132,6 +156,7 @@ func (c *JobPlanCommand) AutocompleteArgs() complete.Predictor {
func (c *JobPlanCommand) Name() string { return "job plan" } func (c *JobPlanCommand) Name() string { return "job plan" }
func (c *JobPlanCommand) Run(args []string) int { func (c *JobPlanCommand) Run(args []string) int {
var diff, policyOverride, verbose bool var diff, policyOverride, verbose bool
var vaultToken, vaultNamespace string
flagSet := c.Meta.FlagSet(c.Name(), FlagSetClient) flagSet := c.Meta.FlagSet(c.Name(), FlagSetClient)
flagSet.Usage = func() { c.Ui.Output(c.Help()) } flagSet.Usage = func() { c.Ui.Output(c.Help()) }
@ -141,6 +166,8 @@ func (c *JobPlanCommand) Run(args []string) int {
flagSet.BoolVar(&c.JobGetter.JSON, "json", false, "") flagSet.BoolVar(&c.JobGetter.JSON, "json", false, "")
flagSet.BoolVar(&c.JobGetter.HCL1, "hcl1", false, "") flagSet.BoolVar(&c.JobGetter.HCL1, "hcl1", false, "")
flagSet.BoolVar(&c.JobGetter.Strict, "hcl2-strict", true, "") 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.Vars, "var", "")
flagSet.Var(&c.JobGetter.VarFiles, "var-file", "") flagSet.Var(&c.JobGetter.VarFiles, "var-file", "")
@ -186,6 +213,22 @@ func (c *JobPlanCommand) Run(args []string) int {
client.SetNamespace(*n) 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 // Setup the options
opts := &api.PlanOptions{} opts := &api.PlanOptions{}
if diff { if diff {

View file

@ -11,6 +11,7 @@ import (
"github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/testutil" "github.com/hashicorp/nomad/testutil"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/shoenig/test/must"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -156,6 +157,60 @@ job "job1" {
ui.ErrorWriter.Reset() 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) { func TestPlanCommand_From_URL(t *testing.T) {
ci.Parallel(t) ci.Parallel(t)
ui := cli.NewMockUi() ui := cli.NewMockUi()

View file

@ -48,6 +48,10 @@ Plan will return one of the following exit codes:
- 1: Allocations created or destroyed. - 1: Allocations created or destroyed.
- 255: Error determining plan results. - 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` When ACLs are enabled, this command requires a token with the `submit-job`
capability for the job's namespace. capability for the job's namespace.
@ -73,6 +77,20 @@ capability for the job's namespace.
a variable has been supplied which is not defined within the root variables. a variable has been supplied which is not defined within the root variables.
Defaults to true. 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=<key=value>`: Variable for template, can be used multiple times.
- `-var-file=<path>`: Path to HCL2 file containing user variables. - `-var-file=<path>`: Path to HCL2 file containing user variables.
@ -241,3 +259,5 @@ if a change is detected.
[`go-getter`]: https://github.com/hashicorp/go-getter [`go-getter`]: https://github.com/hashicorp/go-getter
[`nomad job run -check-index`]: /docs/commands/job/run#check-index [`nomad job run -check-index`]: /docs/commands/job/run#check-index
[`tee`]: https://man7.org/linux/man-pages/man1/tee.1.html [`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