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:
parent
1901cfaba8
commit
ba5c45ab93
3
.changelog/14088.txt
Normal file
3
.changelog/14088.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
cli: Fixed a bug where vault token not respected in plan command
|
||||||
|
```
|
|
@ -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 {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue