From 70b1862026cfe28728385d771a641566d802c090 Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Mon, 23 Oct 2023 20:00:55 -0400 Subject: [PATCH] test: add E2E `vaultcompat` test for JWT auth flow (#18822) Test the JWT auth flow using real Nomad and Vault agents. --- command/job_plan_test.go | 3 +- command/job_validate_test.go | 3 +- e2e/vaultcompat/cluster_setup_test.go | 84 ++++++ e2e/vaultcompat/input/cat_jwt.hcl | 111 ++++++++ .../input/{policy.hcl => policy_legacy.hcl} | 0 e2e/vaultcompat/role_test.go | 14 - e2e/vaultcompat/vaultcompat_test.go | 246 +++++++++++++++--- testutil/server.go | 24 +- testutil/vault.go | 19 +- 9 files changed, 449 insertions(+), 55 deletions(-) create mode 100644 e2e/vaultcompat/cluster_setup_test.go create mode 100644 e2e/vaultcompat/input/cat_jwt.hcl rename e2e/vaultcompat/input/{policy.hcl => policy_legacy.hcl} (100%) delete mode 100644 e2e/vaultcompat/role_test.go diff --git a/command/job_plan_test.go b/command/job_plan_test.go index 3154d6520..61895d54a 100644 --- a/command/job_plan_test.go +++ b/command/job_plan_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/ci" + "github.com/hashicorp/nomad/helper/pointer" "github.com/hashicorp/nomad/testutil" "github.com/mitchellh/cli" "github.com/shoenig/test/must" @@ -188,7 +189,7 @@ func TestPlanCommand_From_Files(t *testing.T) { s := testutil.NewTestServer(t, func(c *testutil.TestServerConfig) { c.Vault.Address = v.HTTPAddr c.Vault.Enabled = true - c.Vault.AllowUnauthenticated = false + c.Vault.AllowUnauthenticated = pointer.Of(false) c.Vault.Token = v.RootToken }) defer s.Stop() diff --git a/command/job_validate_test.go b/command/job_validate_test.go index 00193a776..9fa5b01db 100644 --- a/command/job_validate_test.go +++ b/command/job_validate_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/hashicorp/nomad/ci" + "github.com/hashicorp/nomad/helper/pointer" "github.com/hashicorp/nomad/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" @@ -29,7 +30,7 @@ func TestValidateCommand_Files(t *testing.T) { s := testutil.NewTestServer(t, func(c *testutil.TestServerConfig) { c.Vault.Address = v.HTTPAddr c.Vault.Enabled = true - c.Vault.AllowUnauthenticated = false + c.Vault.AllowUnauthenticated = pointer.Of(false) c.Vault.Token = v.RootToken }) defer s.Stop() diff --git a/e2e/vaultcompat/cluster_setup_test.go b/e2e/vaultcompat/cluster_setup_test.go new file mode 100644 index 000000000..cb2fdd4b2 --- /dev/null +++ b/e2e/vaultcompat/cluster_setup_test.go @@ -0,0 +1,84 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package vaultcompat + +import "fmt" + +const ( + // jwtPath is where the JWT auth method is mounted in Vault. + // Use a non-default value for a more realistic scenario. + jwtPath = "nomad_jwt" +) + +// roleLegacy is the legacy recommendation for nomad cluster role. +var roleLegacy = map[string]interface{}{ + "disallowed_policies": "nomad-server", + "explicit_max_ttl": 0, // use old name for vault compatibility + "name": "nomad-cluster", + "orphan": false, + "period": 259200, // use old name for vault compatibility + "renewable": true, +} + +// authConfigJWT is the configuration for the JWT auth method used by Nomad. +func authConfigJWT(jwksURL string) map[string]any { + return map[string]any{ + "jwks_url": jwksURL, + "jwt_supported_algs": []string{"EdDSA"}, + "default_role": "nomad-workloads", + } +} + +// roleWID is the recommended role for Nomad workloads when using JWT and +// workload identity. +func roleWID(policies []string) map[string]any { + return map[string]any{ + "role_type": "jwt", + "bound_audiences": "vault.io", + "user_claim": "/nomad_job_id", + "user_claim_json_pointer": true, + "claim_mappings": map[string]any{ + "nomad_namespace": "nomad_namespace", + "nomad_job_id": "nomad_job_id", + }, + "token_ttl": "30m", + "token_type": "service", + "token_period": "72h", + "token_policies": policies, + } +} + +// policyWID is a templated Vault policy that grants tasks access to secret +// paths prefixed by /. +func policyWID(mountAccessor string) string { + return fmt.Sprintf(` +path "secret/data/{{identity.entity.aliases.%[1]s.metadata.nomad_namespace}}/{{identity.entity.aliases.%[1]s.metadata.nomad_job_id}}/*" { + capabilities = ["read"] +} + +path "secret/data/{{identity.entity.aliases.%[1]s.metadata.nomad_namespace}}/{{identity.entity.aliases.%[1]s.metadata.nomad_job_id}}" { + capabilities = ["read"] +} + +path "secret/metadata/{{identity.entity.aliases.%[1]s.metadata.nomad_namespace}}/*" { + capabilities = ["list"] +} + +path "secret/metadata/*" { + capabilities = ["list"] +} +`, mountAccessor) +} + +// policyRestricted is Vault policy that only grants read access to a specific +// path. +const policyRestricted = ` +path "secret/data/restricted" { + capabilities = ["read"] +} + +path "secret/metadata/restricted" { + capabilities = ["list"] +} +` diff --git a/e2e/vaultcompat/input/cat_jwt.hcl b/e2e/vaultcompat/input/cat_jwt.hcl new file mode 100644 index 000000000..11452b4ce --- /dev/null +++ b/e2e/vaultcompat/input/cat_jwt.hcl @@ -0,0 +1,111 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: BUSL-1.1 + +job "cat_jwt" { + type = "batch" + + // Tasks in this group are expected to succeed and run to completion. + group "success" { + vault {} + + // Task default_identity uses the default workload identity injected by the + // server and the inherits the Vault configuration from the group. + task "default_identity" { + driver = "raw_exec" + + config { + command = "cat" + args = ["${NOMAD_SECRETS_DIR}/secret.txt"] + } + + template { + data = <