Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport of NET-581 - Added vault namespace in helm into release/1.2.x #2927

3 changes: 3 additions & 0 deletions .changelog/2841.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
vault: Add `namespace` to `secretsBackend.vault.connectCA` in Helm chart. This provides a more convenient way to specify the Vault namespace than nested JSON in `connectCA.additionalConfig`.
```
268 changes: 266 additions & 2 deletions acceptance/tests/vault/vault_namespaces_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,274 @@ import (
"github.com/stretchr/testify/require"
)

// TestVault_VaultNamespace_AdditionalConfig installs Vault, configures a Vault namespace, and then bootstraps it
// with secrets, policies, and Kube Auth Method.
// It then configures Consul to use vault as the backend and checks that it works
// with the vault namespace. Namespace is added in this via global.secretsBackend.vault.connectCA.additionalConfig
func TestVault_VaultNamespace_AdditionalConfig(t *testing.T) {
cfg := suite.Config()
ctx := suite.Environment().DefaultContext(t)
ns := ctx.KubectlOptions(t).Namespace

ver, err := version.NewVersion("1.12.0")
require.NoError(t, err)
if cfg.ConsulVersion != nil && cfg.ConsulVersion.LessThan(ver) {
t.Skipf("skipping this test because vault secrets backend is not supported in version %v", cfg.ConsulVersion.String())
}

vaultNamespacePath := "test-namespace"
consulReleaseName := helpers.RandomName()
vaultReleaseName := helpers.RandomName()

k8sClient := environment.KubernetesClientFromOptions(t, ctx.KubectlOptions(t))
vaultLicenseSecretName := fmt.Sprintf("%s-enterprise-license", vaultReleaseName)
vaultLicenseSecretKey := "license"

vaultEnterpriseLicense := os.Getenv("VAULT_LICENSE")

logger.Log(t, "Creating secret for Vault license")
consul.CreateK8sSecret(t, k8sClient, cfg, ns, vaultLicenseSecretName, vaultLicenseSecretKey, vaultEnterpriseLicense)
vaultHelmvalues := map[string]string{
"server.image.repository": "docker.mirror.hashicorp.services/hashicorp/vault-enterprise",
"server.image.tag": "1.9.4-ent",
"server.enterpriseLicense.secretName": vaultLicenseSecretName,
"server.enterpriseLicense.secretKey": vaultLicenseSecretKey,
}
vaultCluster := vault.NewVaultCluster(t, ctx, cfg, vaultReleaseName, vaultHelmvalues)
vaultCluster.Create(t, ctx, vaultNamespacePath)
// Vault is now installed in the cluster.

// Now fetch the Vault client so we can create the policies and secrets.
vaultClient := vaultCluster.VaultClient(t)

// -------------------------
// PKI
// -------------------------
// Configure Service Mesh CA
connectCAPolicy := "connect-ca-dc1"
connectCARootPath := "connect_root"
connectCAIntermediatePath := "dc1/connect_inter"
// Configure Policy for Connect CA
vault.CreateConnectCARootAndIntermediatePKIPolicy(t, vaultClient, connectCAPolicy, connectCARootPath, connectCAIntermediatePath)

// Configure Server PKI
serverPKIConfig := &vault.PKIAndAuthRoleConfiguration{
BaseURL: "pki",
PolicyName: "consul-ca-policy",
RoleName: "consul-ca-role",
KubernetesNamespace: ns,
DataCenter: "dc1",
ServiceAccountName: fmt.Sprintf("%s-consul-%s", consulReleaseName, "server"),
AllowedSubdomain: fmt.Sprintf("%s-consul-%s", consulReleaseName, "server"),
MaxTTL: "1h",
AuthMethodPath: "kubernetes",
}
serverPKIConfig.ConfigurePKIAndAuthRole(t, vaultClient)

// -------------------------
// KV2 secrets
// -------------------------
// Gossip key
gossipKey, err := vault.GenerateGossipSecret()
require.NoError(t, err)
gossipSecret := &vault.KV2Secret{
Path: "consul/data/secret/gossip",
Key: "gossip",
Value: gossipKey,
PolicyName: "gossip",
}
gossipSecret.SaveSecretAndAddReadPolicy(t, vaultClient)

// License
licenseSecret := &vault.KV2Secret{
Path: "consul/data/secret/license",
Key: "license",
Value: cfg.EnterpriseLicense,
PolicyName: "license",
}
if cfg.EnableEnterprise {
licenseSecret.SaveSecretAndAddReadPolicy(t, vaultClient)
}

//Bootstrap Token
bootstrapToken, err := uuid.GenerateUUID()
require.NoError(t, err)
bootstrapTokenSecret := &vault.KV2Secret{
Path: "consul/data/secret/bootstrap",
Key: "token",
Value: bootstrapToken,
PolicyName: "bootstrap",
}
bootstrapTokenSecret.SaveSecretAndAddReadPolicy(t, vaultClient)

// -------------------------
// Additional Auth Roles
// -------------------------
serverPolicies := fmt.Sprintf("%s,%s,%s,%s", gossipSecret.PolicyName, connectCAPolicy, serverPKIConfig.PolicyName, bootstrapTokenSecret.PolicyName)
if cfg.EnableEnterprise {
serverPolicies += fmt.Sprintf(",%s", licenseSecret.PolicyName)
}

// server
consulServerRole := "server"
srvAuthRoleConfig := &vault.KubernetesAuthRoleConfiguration{
ServiceAccountName: serverPKIConfig.ServiceAccountName,
KubernetesNamespace: ns,
AuthMethodPath: "kubernetes",
RoleName: consulServerRole,
PolicyNames: serverPolicies,
}
srvAuthRoleConfig.ConfigureK8SAuthRole(t, vaultClient)

// client
consulClientRole := ClientRole
consulClientServiceAccountName := fmt.Sprintf("%s-consul-%s", consulReleaseName, ClientRole)
clientAuthRoleConfig := &vault.KubernetesAuthRoleConfiguration{
ServiceAccountName: consulClientServiceAccountName,
KubernetesNamespace: ns,
AuthMethodPath: "kubernetes",
RoleName: consulClientRole,
PolicyNames: gossipSecret.PolicyName,
}
clientAuthRoleConfig.ConfigureK8SAuthRole(t, vaultClient)

// manageSystemACLs
manageSystemACLsRole := ManageSystemACLsRole
manageSystemACLsServiceAccountName := fmt.Sprintf("%s-consul-%s", consulReleaseName, ManageSystemACLsRole)
aclAuthRoleConfig := &vault.KubernetesAuthRoleConfiguration{
ServiceAccountName: manageSystemACLsServiceAccountName,
KubernetesNamespace: ns,
AuthMethodPath: "kubernetes",
RoleName: manageSystemACLsRole,
PolicyNames: bootstrapTokenSecret.PolicyName,
}
aclAuthRoleConfig.ConfigureK8SAuthRole(t, vaultClient)

// allow all components to access server ca
srvCAAuthRoleConfig := &vault.KubernetesAuthRoleConfiguration{
ServiceAccountName: "*",
KubernetesNamespace: ns,
AuthMethodPath: "kubernetes",
RoleName: serverPKIConfig.RoleName,
PolicyNames: serverPKIConfig.PolicyName,
}
srvCAAuthRoleConfig.ConfigureK8SAuthRole(t, vaultClient)

vaultCASecret := vault.CASecretName(vaultReleaseName)

consulHelmValues := map[string]string{
"server.extraVolumes[0].type": "secret",
"server.extraVolumes[0].name": vaultCASecret,
"server.extraVolumes[0].load": "false",

"connectInject.enabled": "true",
"connectInject.replicas": "1",

"global.secretsBackend.vault.enabled": "true",
"global.secretsBackend.vault.consulServerRole": consulServerRole,
"global.secretsBackend.vault.consulClientRole": consulClientRole,
"global.secretsBackend.vault.consulCARole": serverPKIConfig.RoleName,
"global.secretsBackend.vault.manageSystemACLsRole": manageSystemACLsRole,

"global.secretsBackend.vault.ca.secretName": vaultCASecret,
"global.secretsBackend.vault.ca.secretKey": "tls.crt",

"global.secretsBackend.vault.connectCA.address": vaultCluster.Address(),
"global.secretsBackend.vault.connectCA.rootPKIPath": connectCARootPath,
"global.secretsBackend.vault.connectCA.intermediatePKIPath": connectCAIntermediatePath,
"global.secretsBackend.vault.connectCA.additionalConfig": fmt.Sprintf(`"{\"connect\": [{ \"ca_config\": [{ \"namespace\": \"%s\"}]}]}"`, vaultNamespacePath),

"global.secretsBackend.vault.agentAnnotations": fmt.Sprintf("\"vault.hashicorp.com/namespace\": \"%s\"", vaultNamespacePath),

"global.acls.manageSystemACLs": "true",
"global.acls.bootstrapToken.secretName": bootstrapTokenSecret.Path,
"global.acls.bootstrapToken.secretKey": bootstrapTokenSecret.Key,
"global.tls.enabled": "true",
"global.gossipEncryption.secretName": gossipSecret.Path,
"global.gossipEncryption.secretKey": gossipSecret.Key,

"ingressGateways.enabled": "true",
"ingressGateways.defaults.replicas": "1",
"terminatingGateways.enabled": "true",
"terminatingGateways.defaults.replicas": "1",

"server.serverCert.secretName": serverPKIConfig.CertPath,
"global.tls.caCert.secretName": serverPKIConfig.CAPath,
"global.tls.enableAutoEncrypt": "true",

// For sync catalog, it is sufficient to check that the deployment is running and ready
// because we only care that get-auto-encrypt-client-ca init container was able
// to talk to the Consul server using the CA from Vault. For this reason,
// we don't need any services to be synced in either direction.
"syncCatalog.enabled": "true",
"syncCatalog.toConsul": "false",
"syncCatalog.toK8S": "false",
}

if cfg.EnableEnterprise {
consulHelmValues["global.enterpriseLicense.secretName"] = licenseSecret.Path
consulHelmValues["global.enterpriseLicense.secretKey"] = licenseSecret.Key
}

logger.Log(t, "Installing Consul")
consulCluster := consul.NewHelmCluster(t, consulHelmValues, ctx, cfg, consulReleaseName)
consulCluster.Create(t)

// Validate that the gossip encryption key is set correctly.
logger.Log(t, "Validating the gossip key has been set correctly.")
consulCluster.ACLToken = bootstrapToken
consulClient, _ := consulCluster.SetupConsulClient(t, true)
keys, err := consulClient.Operator().KeyringList(nil)
require.NoError(t, err)
// There are two identical keys for LAN and WAN since there is only 1 dc.
require.Len(t, keys, 2)
require.Equal(t, 1, keys[0].PrimaryKeys[gossipKey])

// Confirm that the Vault Connect CA has been bootstrapped correctly.
caConfig, _, err := consulClient.Connect().CAGetConfig(nil)
require.NoError(t, err)
require.Equal(t, caConfig.Provider, "vault")

// Validate that consul sever is running correctly and the consul members command works
logger.Log(t, "Confirming that we can run Consul commands when exec'ing into server container")
membersOutput, err := k8s.RunKubectlAndGetOutputWithLoggerE(t, ctx.KubectlOptions(t), terratestLogger.Discard, "exec", fmt.Sprintf("%s-consul-server-0", consulReleaseName), "-c", "consul", "--", "sh", "-c", fmt.Sprintf("CONSUL_HTTP_TOKEN=%s consul members", bootstrapToken))
logger.Logf(t, "Members: \n%s", membersOutput)
require.NoError(t, err)
require.Contains(t, membersOutput, fmt.Sprintf("%s-consul-server-0", consulReleaseName))

if cfg.EnableEnterprise {
// Validate that the enterprise license is set correctly.
logger.Log(t, "Validating the enterprise license has been set correctly.")
license, licenseErr := consulClient.Operator().LicenseGet(nil)
require.NoError(t, licenseErr)
require.True(t, license.Valid)
}

// Deploy two services and check that they can talk to each other.
logger.Log(t, "creating static-server and static-client deployments")
k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject")
if cfg.EnableTransparentProxy {
k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy")
} else {
k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-inject")
}
helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() {
k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), "../fixtures/bases/intention")
})
k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../fixtures/bases/intention")

logger.Log(t, "checking that connection is successful")
if cfg.EnableTransparentProxy {
k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), StaticClientName, "http://static-server")
} else {
k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), StaticClientName, "http://localhost:1234")
}
}

// TestVault_VaultNamespace installs Vault, configures a Vault namespace, and then bootstraps it
// with secrets, policies, and Kube Auth Method.
// It then configures Consul to use vault as the backend and checks that it works
// with the vault namespace.
// with the vault namespace. Namespace is added in this via global.secretsBackend.vault.connectCA.namespace
func TestVault_VaultNamespace(t *testing.T) {
cfg := suite.Config()
ctx := suite.Environment().DefaultContext(t)
Expand Down Expand Up @@ -195,7 +459,7 @@ func TestVault_VaultNamespace(t *testing.T) {
"global.secretsBackend.vault.connectCA.address": vaultCluster.Address(),
"global.secretsBackend.vault.connectCA.rootPKIPath": connectCARootPath,
"global.secretsBackend.vault.connectCA.intermediatePKIPath": connectCAIntermediatePath,
"global.secretsBackend.vault.connectCA.additionalConfig": fmt.Sprintf(`"{\"connect\": [{ \"ca_config\": [{ \"namespace\": \"%s\"}]}]}"`, vaultNamespacePath),
"global.secretsBackend.vault.connectCA.namespace": vaultNamespacePath,

"global.secretsBackend.vault.agentAnnotations": fmt.Sprintf("\"vault.hashicorp.com/namespace\": \"%s\"", vaultNamespacePath),

Expand Down
3 changes: 3 additions & 0 deletions charts/consul/templates/server-config-configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ data:
"ca_file": "/consul/vault-ca/tls.crt",
{{- end }}
"intermediate_pki_path": "{{ .connectCA.intermediatePKIPath }}",
{{- if .connectCA.namespace }}
"namespace": "{{ .connectCA.namespace }}",
{{- end }}
"root_pki_path": "{{ .connectCA.rootPKIPath }}",
"auth_method": {
"type": "kubernetes",
Expand Down
37 changes: 37 additions & 0 deletions charts/consul/test/unit/server-config-configmap.bats
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,43 @@ load _helpers
[ "${actual}" = "true" ]
}

@test "server/ConfigMap: doesn't set Vault Namespace in connect CA config when connectCA.namespace is blank in values.yaml" {
cd `chart_dir`

local actual=$(helm template \
-s templates/server-config-configmap.yaml \
--set 'global.secretsBackend.vault.enabled=true' \
--set 'global.secretsBackend.vault.consulServerRole=foo' \
--set 'global.secretsBackend.vault.consulClientRole=foo' \
--set 'global.secretsBackend.vault.connectCA.address=example.com' \
--set 'global.secretsBackend.vault.connectCA.rootPKIPath=root' \
--set 'global.secretsBackend.vault.connectCA.intermediatePKIPath=int' \
--set 'global.secretsBackend.vault.ca.secretName=ca' \
--set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \
. | tee /dev/stderr |
yq '.data["connect-ca-config.json"] | contains("namespace")' | tee /dev/stderr)
[ "${actual}" = "false" ]
}

@test "server/ConfigMap: set Vault Namespace in connect CA config when connectCA.namespace is not blank in values.yaml" {
cd `chart_dir`

local actual=$(helm template \
-s templates/server-config-configmap.yaml \
--set 'global.secretsBackend.vault.enabled=true' \
--set 'global.secretsBackend.vault.consulServerRole=foo' \
--set 'global.secretsBackend.vault.consulClientRole=foo' \
--set 'global.secretsBackend.vault.connectCA.address=example.com' \
--set 'global.secretsBackend.vault.connectCA.rootPKIPath=root' \
--set 'global.secretsBackend.vault.connectCA.intermediatePKIPath=int' \
--set 'global.secretsBackend.vault.ca.secretName=ca' \
--set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \
--set 'global.secretsBackend.vault.connectCA.namespace=vault-namespace' \
. | tee /dev/stderr |
yq '.data["connect-ca-config.json"] | contains("\"namespace\": \"vault-namespace\"")' | tee /dev/stderr)
[ "${actual}" = "true" ]
}

@test "server/ConfigMap: doesn't add federation config when global.federation.enabled is false (default)" {
cd `chart_dir`
local actual=$(helm template \
Expand Down
5 changes: 5 additions & 0 deletions charts/consul/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ global:
# secretKey should be in the form of "key".
secretsBackend:
vault:

# Enabling the Vault secrets backend will replace Kubernetes secrets with referenced Vault secrets.
enabled: false

Expand Down Expand Up @@ -205,6 +206,10 @@ global:
# Please refer to [Vault ACL policies](https://developer.hashicorp.com/consul/docs/connect/ca/vault#vault-acl-policies)
# documentation for information on how to configure the Vault policies.
connectCA:
# Vault namespace (optional).
# For more details, please refer to [Vault Connect CA configuration](https://developer.hashicorp.com/consul/docs/connect/ca/vault#namespace)
namespace: ""

# The address of the Vault server.
address: ""

Expand Down