cli: correctly use and validate job with vault token set
This PR fixes `job validate` to respect '-vault-token', '$VAULT_TOKEN', '-vault-namespace' if set.
This commit is contained in:
parent
b72ff42ada
commit
fc58f4972c
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
cli: Fixed a bug where job validate did not respect vault token or namespace
|
||||||
|
```
|
|
@ -2,11 +2,13 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/hashicorp/nomad/api"
|
"github.com/hashicorp/nomad/api"
|
||||||
"github.com/hashicorp/nomad/command/agent"
|
"github.com/hashicorp/nomad/command/agent"
|
||||||
|
"github.com/hashicorp/nomad/helper"
|
||||||
"github.com/hashicorp/nomad/nomad/structs"
|
"github.com/hashicorp/nomad/nomad/structs"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
)
|
)
|
||||||
|
@ -28,6 +30,10 @@ Alias: nomad validate
|
||||||
it is read from the file at the supplied path or downloaded and
|
it is read from the file at the supplied path or downloaded and
|
||||||
read from URL specified.
|
read from URL specified.
|
||||||
|
|
||||||
|
The run 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 'read-job'
|
When ACLs are enabled, this command requires a token with the 'read-job'
|
||||||
capability for the job's namespace.
|
capability for the job's namespace.
|
||||||
|
|
||||||
|
@ -50,6 +56,22 @@ Validate Options:
|
||||||
has been supplied which is not defined within the root variables. Defaults
|
has been supplied which is not defined within the root variables. Defaults
|
||||||
to true.
|
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 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.
|
||||||
|
|
||||||
|
@ -65,10 +87,12 @@ func (c *JobValidateCommand) Synopsis() string {
|
||||||
|
|
||||||
func (c *JobValidateCommand) AutocompleteFlags() complete.Flags {
|
func (c *JobValidateCommand) AutocompleteFlags() complete.Flags {
|
||||||
return complete.Flags{
|
return complete.Flags{
|
||||||
"-hcl1": complete.PredictNothing,
|
"-hcl1": complete.PredictNothing,
|
||||||
"-hcl2-strict": complete.PredictNothing,
|
"-hcl2-strict": complete.PredictNothing,
|
||||||
"-var": complete.PredictAnything,
|
"-vault-token": complete.PredictAnything,
|
||||||
"-var-file": complete.PredictFiles("*.var"),
|
"-vault-namespace": complete.PredictAnything,
|
||||||
|
"-var": complete.PredictAnything,
|
||||||
|
"-var-file": complete.PredictFiles("*.var"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,11 +107,15 @@ func (c *JobValidateCommand) AutocompleteArgs() complete.Predictor {
|
||||||
func (c *JobValidateCommand) Name() string { return "job validate" }
|
func (c *JobValidateCommand) Name() string { return "job validate" }
|
||||||
|
|
||||||
func (c *JobValidateCommand) Run(args []string) int {
|
func (c *JobValidateCommand) Run(args []string) int {
|
||||||
|
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()) }
|
||||||
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", "")
|
||||||
|
|
||||||
|
@ -127,6 +155,20 @@ func (c *JobValidateCommand) Run(args []string) int {
|
||||||
client.SetRegion(*r)
|
client.SetRegion(*r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse the Vault token
|
||||||
|
if vaultToken == "" {
|
||||||
|
// Check the environment variable
|
||||||
|
vaultToken = os.Getenv("VAULT_TOKEN")
|
||||||
|
}
|
||||||
|
|
||||||
|
if vaultToken != "" {
|
||||||
|
job.VaultToken = helper.StringToPtr(vaultToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vaultNamespace != "" {
|
||||||
|
job.VaultNamespace = helper.StringToPtr(vaultNamespace)
|
||||||
|
}
|
||||||
|
|
||||||
// Check that the job is valid
|
// Check that the job is valid
|
||||||
jr, _, err := client.Jobs().Validate(job, nil)
|
jr, _, err := client.Jobs().Validate(job, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -17,44 +17,57 @@ func TestValidateCommand_Implements(t *testing.T) {
|
||||||
var _ cli.Command = &JobValidateCommand{}
|
var _ cli.Command = &JobValidateCommand{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateCommand(t *testing.T) {
|
func TestValidateCommand_Files(t *testing.T) {
|
||||||
ci.Parallel(t)
|
ci.Parallel(t)
|
||||||
// Create a server
|
|
||||||
s := testutil.NewTestServer(t, nil)
|
// 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()
|
defer s.Stop()
|
||||||
|
|
||||||
ui := cli.NewMockUi()
|
t.Run("basic", func(t *testing.T) {
|
||||||
cmd := &JobValidateCommand{Meta: Meta{Ui: ui, flagAddress: "http://" + s.HTTPAddr}}
|
ui := cli.NewMockUi()
|
||||||
|
cmd := &JobValidateCommand{Meta: Meta{Ui: ui, flagAddress: "http://" + s.HTTPAddr}}
|
||||||
|
args := []string{"testdata/example-basic.nomad"}
|
||||||
|
code := cmd.Run(args)
|
||||||
|
require.Equal(t, 0, code)
|
||||||
|
})
|
||||||
|
|
||||||
fh, err := ioutil.TempFile("", "nomad")
|
t.Run("vault no token", func(t *testing.T) {
|
||||||
if err != nil {
|
ui := cli.NewMockUi()
|
||||||
t.Fatalf("err: %s", err)
|
cmd := &JobValidateCommand{Meta: Meta{Ui: ui}}
|
||||||
}
|
args := []string{"-address", "http://" + s.HTTPAddr, "testdata/example-vault.nomad"}
|
||||||
defer os.Remove(fh.Name())
|
code := cmd.Run(args)
|
||||||
_, err = fh.WriteString(`
|
require.Contains(t, ui.ErrorWriter.String(), "* Vault used in the job but missing Vault token")
|
||||||
job "job1" {
|
require.Equal(t, 1, code)
|
||||||
type = "service"
|
})
|
||||||
datacenters = [ "dc1" ]
|
|
||||||
group "group1" {
|
t.Run("vault bad token via flag", func(t *testing.T) {
|
||||||
count = 1
|
ui := cli.NewMockUi()
|
||||||
task "task1" {
|
cmd := &JobValidateCommand{Meta: Meta{Ui: ui}}
|
||||||
driver = "exec"
|
args := []string{"-address", "http://" + s.HTTPAddr, "-vault-token=abc123", "testdata/example-vault.nomad"}
|
||||||
config {
|
code := cmd.Run(args)
|
||||||
command = "/bin/sleep"
|
require.Contains(t, ui.ErrorWriter.String(), "* bad token")
|
||||||
}
|
require.Equal(t, 1, code)
|
||||||
resources {
|
})
|
||||||
cpu = 1000
|
|
||||||
memory = 512
|
t.Run("vault token bad via env", func(t *testing.T) {
|
||||||
}
|
t.Setenv("VAULT_TOKEN", "abc123")
|
||||||
}
|
ui := cli.NewMockUi()
|
||||||
}
|
cmd := &JobValidateCommand{Meta: Meta{Ui: ui}}
|
||||||
}`)
|
args := []string{"-address", "http://" + s.HTTPAddr, "testdata/example-vault.nomad"}
|
||||||
if err != nil {
|
code := cmd.Run(args)
|
||||||
t.Fatalf("err: %s", err)
|
require.Contains(t, ui.ErrorWriter.String(), "* bad token")
|
||||||
}
|
require.Equal(t, 1, code)
|
||||||
if code := cmd.Run([]string{fh.Name()}); code != 0 {
|
})
|
||||||
t.Fatalf("expect exit 0, got: %d: %s", code, ui.ErrorWriter.String())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateCommand_Fails(t *testing.T) {
|
func TestValidateCommand_Fails(t *testing.T) {
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
job "job1" {
|
||||||
|
type = "service"
|
||||||
|
datacenters = ["dc1"]
|
||||||
|
group "group1" {
|
||||||
|
count = 1
|
||||||
|
task "task1" {
|
||||||
|
driver = "exec"
|
||||||
|
config {
|
||||||
|
command = "/bin/sleep"
|
||||||
|
}
|
||||||
|
resources {
|
||||||
|
cpu = 1000
|
||||||
|
memory = 512
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
job "vault" {
|
||||||
|
datacenters = ["dc1"]
|
||||||
|
group "group" {
|
||||||
|
task "task" {
|
||||||
|
driver = "docker"
|
||||||
|
config {
|
||||||
|
image = "alpine:latest"
|
||||||
|
}
|
||||||
|
vault {
|
||||||
|
policies = ["my-policy"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ import (
|
||||||
vapi "github.com/hashicorp/vault/api"
|
vapi "github.com/hashicorp/vault/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
// jobVaultHook is an job registration admission controllver for Vault blocks.
|
// jobVaultHook is an job registration admission controller for Vault blocks.
|
||||||
type jobVaultHook struct {
|
type jobVaultHook struct {
|
||||||
srv *Server
|
srv *Server
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ func (h jobVaultHook) Validate(job *structs.Job) ([]error, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// validatePolicies returns an error if the job contains Vault blocks that
|
// validatePolicies returns an error if the job contains Vault blocks that
|
||||||
// require policies that the requirest token is not allowed to access.
|
// require policies that the request token is not allowed to access.
|
||||||
func (jobVaultHook) validatePolicies(
|
func (jobVaultHook) validatePolicies(
|
||||||
blocks map[string]map[string]*structs.Vault,
|
blocks map[string]map[string]*structs.Vault,
|
||||||
token *vapi.Secret,
|
token *vapi.Secret,
|
||||||
|
|
|
@ -81,7 +81,10 @@ type ClientConfig struct {
|
||||||
|
|
||||||
// VaultConfig is used to configure Vault
|
// VaultConfig is used to configure Vault
|
||||||
type VaultConfig struct {
|
type VaultConfig struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
AllowUnauthenticated bool `json:"allow_unauthenticated"`
|
||||||
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACLConfig is used to configure ACLs
|
// ACLConfig is used to configure ACLs
|
||||||
|
@ -114,7 +117,8 @@ func defaultServerConfig() (*TestServerConfig, []int) {
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
},
|
},
|
||||||
Vault: &VaultConfig{
|
Vault: &VaultConfig{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
|
AllowUnauthenticated: true,
|
||||||
},
|
},
|
||||||
ACL: &ACLConfig{
|
ACL: &ACLConfig{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
|
|
|
@ -29,6 +29,10 @@ supports `go-getter` syntax.
|
||||||
On successful validation, exit code 0 will be returned, otherwise an exit code
|
On successful validation, exit code 0 will be returned, otherwise an exit code
|
||||||
of 1 indicates an error.
|
of 1 indicates an error.
|
||||||
|
|
||||||
|
The run 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 `read-job`
|
When ACLs are enabled, this command requires a token with the `read-job`
|
||||||
capability for the job's namespace.
|
capability for the job's namespace.
|
||||||
|
|
||||||
|
@ -48,6 +52,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 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.
|
- `-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.
|
||||||
|
@ -79,3 +97,5 @@ Job validation successful
|
||||||
|
|
||||||
[`go-getter`]: https://github.com/hashicorp/go-getter
|
[`go-getter`]: https://github.com/hashicorp/go-getter
|
||||||
[job specification]: /docs/job-specification
|
[job specification]: /docs/job-specification
|
||||||
|
[`vault` stanza `allow_unauthenticated`]: /docs/configuration/vault#allow_unauthenticated
|
||||||
|
[`vault_token`]: /docs/job-specification/job#vault_token
|
Loading…
Reference in New Issue