open-nomad/nomad/job_endpoint_hook_vault.go
Seth Hoenig 2088ca3345
cleanup more helper updates (#14638)
* cleanup: refactor MapStringStringSliceValueSet to be cleaner

* cleanup: replace SliceStringToSet with actual set

* cleanup: replace SliceStringSubset with real set

* cleanup: replace SliceStringContains with slices.Contains

* cleanup: remove unused function SliceStringHasPrefix

* cleanup: fixup StringHasPrefixInSlice doc string

* cleanup: refactor SliceSetDisjoint to use real set

* cleanup: replace CompareSliceSetString with SliceSetEq

* cleanup: replace CompareMapStringString with maps.Equal

* cleanup: replace CopyMapStringString with CopyMap

* cleanup: replace CopyMapStringInterface with CopyMap

* cleanup: fixup more CopyMapStringString and CopyMapStringInt

* cleanup: replace CopySliceString with slices.Clone

* cleanup: remove unused CopySliceInt

* cleanup: refactor CopyMapStringSliceString to be generic as CopyMapOfSlice

* cleanup: replace CopyMap with maps.Clone

* cleanup: run go mod tidy
2022-09-21 14:53:25 -05:00

95 lines
2.2 KiB
Go

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
}