open-nomad/nomad/job_endpoint_hook_vault.go

98 lines
2.3 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package nomad
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/nomad/structs"
vapi "github.com/hashicorp/vault/api"
"golang.org/x/exp/slices"
)
// jobVaultHook is an job registration admission controller for Vault blocks.
type jobVaultHook struct {
srv *Server
}
func (jobVaultHook) Name() string {
return "vault"
}
func (h jobVaultHook) Validate(job *structs.Job) ([]error, error) {
vaultBlocks := job.Vault()
if len(vaultBlocks) == 0 {
return nil, nil
}
vconf := h.srv.config.VaultConfig
if !vconf.IsEnabled() {
return nil, fmt.Errorf("Vault not enabled but used in the job")
}
// Return early if Vault configuration doesn't require authentication.
if vconf.AllowsUnauthenticated() {
return nil, nil
}
// At this point the job has a vault block and the server requires
// authentication, so check if the user has the right permissions.
if job.VaultToken == "" {
return nil, fmt.Errorf("Vault used in the job but missing Vault token")
}
tokenSecret, err := h.srv.vault.LookupToken(context.Background(), job.VaultToken)
if err != nil {
return nil, fmt.Errorf("failed to lookup Vault token: %v", err)
}
// Check namespaces.
err = h.validateNamespaces(vaultBlocks, tokenSecret)
if err != nil {
return nil, err
}
// Check policies.
err = h.validatePolicies(vaultBlocks, tokenSecret)
if err != nil {
return nil, err
}
return nil, nil
}
// validatePolicies returns an error if the job contains Vault blocks that
// require policies that the request token is not allowed to access.
func (jobVaultHook) validatePolicies(
blocks map[string]map[string]*structs.Vault,
token *vapi.Secret,
) error {
jobPolicies := structs.VaultPoliciesSet(blocks)
if len(jobPolicies) == 0 {
return nil
}
allowedPolicies, err := token.TokenPolicies()
if err != nil {
return fmt.Errorf("failed to lookup Vault token policies: %v", err)
}
// If we are given a root token it can access all policies
if slices.Contains(allowedPolicies, "root") {
return nil
}
subset, offending := helper.IsSubset(allowedPolicies, jobPolicies)
if !subset {
return fmt.Errorf("Vault token doesn't allow access to the following policies: %s",
strings.Join(offending, ", "))
}
return nil
}