Skip to content

Commit

Permalink
vault: load default config for tasks without vault
Browse files Browse the repository at this point in the history
It is often expected that a task that needs access to Vault defines a
`vault` block to specify the Vault policy to use to derive a token.

But in some scenarios, like when the Nomad client is connected to a
local Vault agent that is responsible for authn/authz, the task is not
required to defined a `vault` block.

In these situations, the `default` Vault cluster should be used to
render the template.
  • Loading branch information
lgfa29 committed Dec 11, 2023
1 parent 268e92e commit bd0626e
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 8 deletions.
13 changes: 5 additions & 8 deletions client/allocrunner/taskrunner/template_hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
cstructs "github.com/hashicorp/nomad/client/structs"
"github.com/hashicorp/nomad/client/taskenv"
"github.com/hashicorp/nomad/nomad/structs"
structsc "github.com/hashicorp/nomad/nomad/structs/config"
)

const (
Expand Down Expand Up @@ -212,14 +211,12 @@ func (h *templateHook) Poststart(ctx context.Context, req *interfaces.TaskPostst
func (h *templateHook) newManager() (unblock chan struct{}, err error) {
unblock = make(chan struct{})

var vaultConfig *structsc.VaultConfig
if h.task.Vault != nil {
vaultCluster := h.task.GetVaultClusterName()
vaultConfig = h.config.clientConfig.GetVaultConfigs(h.logger)[vaultCluster]
vaultCluster := h.task.GetVaultClusterName()
vaultConfig := h.config.clientConfig.GetVaultConfigs(h.logger)[vaultCluster]

if vaultConfig == nil {
return nil, fmt.Errorf("Vault cluster %q is disabled or not configured", vaultCluster)
}
// Fail if task has a vault block but not client config was found.
if h.task.Vault != nil && vaultConfig == nil {
return nil, fmt.Errorf("Vault cluster %q is disabled or not configured", vaultCluster)
}

tg := h.config.alloc.Job.LookupTaskGroup(h.config.alloc.TaskGroup)
Expand Down
132 changes: 132 additions & 0 deletions client/allocrunner/taskrunner/template_hook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ package taskrunner
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"path"
"sync"
"testing"
"time"

consulapi "github.com/hashicorp/consul/api"
"github.com/hashicorp/nomad/ci"
Expand All @@ -17,10 +21,12 @@ import (
"github.com/hashicorp/nomad/client/config"
cstructs "github.com/hashicorp/nomad/client/structs"
"github.com/hashicorp/nomad/client/taskenv"
"github.com/hashicorp/nomad/helper/pointer"
"github.com/hashicorp/nomad/helper/testlog"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/hashicorp/nomad/nomad/mock"
"github.com/hashicorp/nomad/nomad/structs"
structsc "github.com/hashicorp/nomad/nomad/structs/config"
"github.com/shoenig/test/must"
)

Expand Down Expand Up @@ -135,3 +141,129 @@ func Test_templateHook_Prestart_ConsulWI(t *testing.T) {
})
}
}

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

secretsResp := `
{
"data": {
"data": {
"secret": "secret"
},
"metadata": {
"created_time": "2023-10-18T15:58:29.65137Z",
"custom_metadata": null,
"deletion_time": "",
"destroyed": false,
"version": 1
}
}
}`

// Start test server to simulate Vault cluster responses.
reqCh := make(chan any)
defaultVaultServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
reqCh <- struct{}{}
fmt.Fprintln(w, secretsResp)
}))
t.Cleanup(defaultVaultServer.Close)

// Setup client with Vault config.
clientConfig := config.DefaultConfig()
clientConfig.TemplateConfig.DisableSandbox = true
clientConfig.VaultConfigs = map[string]*structsc.VaultConfig{
structs.VaultDefaultCluster: {
Name: structs.VaultDefaultCluster,
Enabled: pointer.Of(true),
Addr: defaultVaultServer.URL,
},
}

testCases := []struct {
name string
vault *structs.Vault
expectedCluster string
}{
{
name: "use default cluster",
vault: &structs.Vault{
Cluster: structs.VaultDefaultCluster,
},
expectedCluster: structs.VaultDefaultCluster,
},
{
name: "use default cluster if no vault block is provided",
vault: nil,
expectedCluster: structs.VaultDefaultCluster,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Setup alloc and task to connect to Vault cluster.
alloc := mock.MinAlloc()
task := alloc.Job.TaskGroups[0].Tasks[0]
task.Vault = tc.vault

// Setup template hook.
taskDir := t.TempDir()
hookConfig := &templateHookConfig{
alloc: alloc,
logger: testlog.HCLogger(t),
lifecycle: trtesting.NewMockTaskHooks(),
events: &trtesting.MockEmitter{},
clientConfig: clientConfig,
envBuilder: taskenv.NewBuilder(mock.Node(), alloc, task, clientConfig.Region),
templates: []*structs.Template{
{
EmbeddedTmpl: `{{with secret "secret/data/test"}}{{.Data.data.secret}}{{end}}`,
ChangeMode: structs.TemplateChangeModeNoop,
DestPath: path.Join(taskDir, "out.txt"),
},
},
}
hook := newTemplateHook(hookConfig)

// Start template hook with a timeout context to ensure it exists.
req := &interfaces.TaskPrestartRequest{
Task: task,
TaskDir: &allocdir.TaskDir{Dir: taskDir},
}

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
t.Cleanup(cancel)

// Start in a goroutine because Prestart() blocks until first
// render.
hookErrCh := make(chan error)
go func() {
err := hook.Prestart(ctx, req, nil)
hookErrCh <- err
}()

var gotRequest bool
LOOP:
for {
select {
// Register mock Vault server received a request.
case <-reqCh:
gotRequest = true

// Verify test doesn't timeout.
case <-ctx.Done():
must.NoError(t, ctx.Err())
return

// Verify hook.Prestart() doesn't errors.
case err := <-hookErrCh:
must.NoError(t, err)
break LOOP
}
}

// Verify mock Vault server received a request.
must.True(t, gotRequest)
})
}
}

0 comments on commit bd0626e

Please sign in to comment.