Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: support vault token in plan command #14088

Merged
merged 1 commit into from
Aug 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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