2088ca3345
* 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
95 lines
2.2 KiB
Go
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
|
|
}
|