From a16c4ee0a02320c812f78bc2f33d192f409f62be Mon Sep 17 00:00:00 2001 From: John Murret Date: Mon, 13 Jun 2022 13:29:29 -0600 Subject: [PATCH] Enable configure Connect Injector and Controller Webhooks to be managed by Vault (#1191) * refactored TestVault * Fixing name of CreateConnectCARootAndIntermediatePIKPolicy to CreateConnectCARootAndIntermediatePKIPolicy * refactored all except WAN Fed has error * fixing vault wan fed test * comment formatting * PR Feedback. Moving vault helper functions to be on the structs they were taking as arguments. * PR Feedback. changing name of Save() onkv2secret to something moredescriptive. adding comment string. * Do not load webhook cert manager when vault is enabled. * do not mount volumes when using vault * configuring vault injector * Fixing linting * Pods are all running * Fixing cert-dir paths for vault and non-vault use * fixing volume mount * fixing volume mount again * adding bats tests for tls cert directory * fixing cert for tls-cert dir * Adding logic to tests for controller tls cert. also adding snapshot agent and vault namespaces tests * it works...with hardocded stuff...need to refactor from here * refactoring out the updating of the webhook config * adding missing file * adding tests for webhook-cert-manager * refining webhook cert manager to remove setting of global.enablePodSecurityPolicies * adding connect-inject bats tests * adding test for controller and use of resource-prefix * adding tests for mwc update code * configure controller to only update mwc with ca bundle when using vault as a secrets backend. * configure connect inject to only update mwc with ca bundle when using vault as a secrets backend. * fixing lint errors for unnahdled errors * embedding tlsCertDir for controller and connectInject under vault in values.yaml * embedding tlsCertDir for controller and connectInject under vault in values.yaml - fixing tests * change vault role for controller to come from global.secretsBackend.vault.consulControllerCARole * fixing vault namespaces and snapshot agent on vault acceptance tests * fixing VAULT_TLSAUtoReload test * rebased fromvault refactor. static server replicaset has error about unknown authority. * Working using separate CAs for connect-injector and controller. only vault test is configured. * adding failure if .Values.global.secretsBackend.vault.consulConnectInjectCARole, .Values.global.secretsBackend.vault.connectInject.tlsCert, .Values.global.secretsBackend.vault.connectInject.caCert are either all not set or all set. * updating chart with caCert configs * enforcing setting both controller and connectInject CA and tls vault settings all at once. * correcting connect inject tests * Update controller tests * fixing condition on which web certmanager is shut off * only rendering vault role when suppled in controller and connect-inject deployments * fixing connect inject deploy for vault role * making global.secretsBackend.vault.consulCARole the fallback in the controller and connect-inject deployments if the web cert replacement roles are not defined. * Updating the doc string for TestVault_WebhookCerts * correct rebasing issues * removing unneccessary format changes. refactoring consul.serverTLSCATemplate to use consul.vaultCATemplate * updating test descriptions for webhook-cert-manager resources * updating connect-inject-clusterrole and controller-clusterrole tests * updated maybeFailValuesIfVaultWebhookCertSettingsAreIncomplete * Adding Changelog * Update maybeFailValuesIfVaultWebhookCertSettingsAreIncomplete to validateVaultWebhookCertConfiguration * Updating wbhook vault test to make sure that webhook-cert-manager is not deployed. * Fixing the validation that webhook-cert-manager is not running in the TestVault_WebhookCerts test * Renamed consulControllerCARole to consulControllerRole and consulContronnectInjectCARole to consulConnectInjectRole * fixing linting * Renamed configureCABundleUpdate() to updateWebhookCABundle() * Make ca.crt a constant * Adding doc strings for webhook certs secretName * updated alt_names for controller and connect inject deployments to be the same as they are under web-cert-manager. updated path of where webhook certs get saved. * Change mutatingwebhookconfigurationswhen to mutatingwebhookconfigurations when * added test cases for vault to controller test * Webhook certs vault test - checking cert rotation. currently failing. * moved vault webhook stuff into main vault test and deleted the webhook cert specific test. * getting rid of lint error * refactoring long conditional in webhook-cert-manager files into a variable for readability * addressing PR feedback * Apply suggestions from code review Co-authored-by: Iryna Shustava * Fixing broken test with retry change * Update charts/consul/templates/_helpers.tpl Co-authored-by: Iryna Shustava * Removing 127.0.0.1 from ip_sans * Removing reference to common_name: Consul Webhook Certificates Service * Removing a dangle reference to Consul Webhook Certificates Service * adding 127.0.0.1 back into server ip_sans * making common name the name of the service for connect-inject and controller * Update the description for enable-webhook-ca-update flag in control-plane/subcommand/controller/command.go Co-authored-by: Iryna Shustava * Dropping the consul prefix from consulConnectInjectRole and consulControllerRole * Updating values.yaml file descriptions for connectInject and controller under vault. * Updating cert expiry in logging in vault test from RPC expiry. Co-authored-by: Iryna Shustava --- CHANGELOG.md | 2 + acceptance/framework/consul/helm_cluster.go | 8 +- acceptance/framework/vault/helpers.go | 4 +- .../tests/controller/controller_test.go | 229 +++++++++++++- acceptance/tests/vault/vault_test.go | 72 ++++- .../tests/vault/vault_tls_auto_reload_test.go | 2 +- charts/consul/templates/_helpers.tpl | 77 ++++- .../templates/connect-inject-clusterrole.yaml | 11 + .../templates/connect-inject-deployment.yaml | 29 ++ .../templates/controller-clusterrole.yaml | 11 + .../templates/controller-deployment.yaml | 30 ++ .../webhook-cert-manager-clusterrole.yaml | 3 +- ...bhook-cert-manager-clusterrolebinding.yaml | 3 +- .../webhook-cert-manager-configmap.yaml | 5 +- .../webhook-cert-manager-deployment.yaml | 3 +- ...ebhook-cert-manager-podsecuritypolicy.yaml | 3 +- .../webhook-cert-manager-serviceaccount.yaml | 3 +- .../test/unit/connect-inject-clusterrole.bats | 133 +++++++++ .../test/unit/connect-inject-deployment.bats | 279 +++++++++++++++++- .../test/unit/controller-clusterrole.bats | 186 ++++++++++++ .../test/unit/controller-deployment.bats | 265 ++++++++++++++++- .../webhook-cert-manager-clusterrole.bats | 111 +++++++ ...bhook-cert-manager-clusterrolebinding.bats | 24 ++ .../unit/webhook-cert-manager-configmap.bats | 26 +- .../unit/webhook-cert-manager-deployment.bats | 24 ++ ...ebhook-cert-manager-podsecuritypolicy.bats | 29 +- .../webhook-cert-manager-serviceaccount.bats | 24 ++ charts/consul/values.yaml | 62 +++- .../mutating_webhook_configuration.go | 51 ++++ .../mutating_webhook_configuration_test.go | 44 +++ .../subcommand/controller/command.go | 59 +++- .../subcommand/inject-connect/command.go | 53 +++- .../webhook-cert-manager/command.go | 46 +-- 33 files changed, 1816 insertions(+), 95 deletions(-) create mode 100644 control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go create mode 100644 control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 4299721a7f..9040b7bc0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## UNRELEASED IMPROVEMENTS: +* Control Plane + * Enable configuring Connect Injector and Controller Webhooks' certificates to be managed by Vault. [[GH-1191](https://github.com/hashicorp/consul-k8s/pull/1191/)] * Helm * Enable the configuring of snapshot intervals in the client snapshot agent via `client.snapshotAgent.interval`. [[GH-1235](https://github.com/hashicorp/consul-k8s/pull/1235)] * Enable configuring the pod topologySpreadConstraints for mesh, terminating, and ingress gateways. [[GH-1257](https://github.com/hashicorp/consul-k8s/pull/1257)] diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index ca752509e9..151f0c852a 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -274,12 +274,16 @@ func (h *HelmCluster) Upgrade(t *testing.T, helmValues map[string]string) { } func (h *HelmCluster) CreatePortForwardTunnel(t *testing.T, remotePort int) string { - localPort := terratestk8s.GetAvailablePort(t) serverPod := fmt.Sprintf("%s-consul-server-0", h.releaseName) + return h.CreatePortForwardTunnelToResourcePort(t, serverPod, remotePort) +} + +func (h *HelmCluster) CreatePortForwardTunnelToResourcePort(t *testing.T, resourceName string, remotePort int) string { + localPort := terratestk8s.GetAvailablePort(t) tunnel := terratestk8s.NewTunnelWithLogger( h.helmOptions.KubectlOptions, terratestk8s.ResourceTypePod, - serverPod, + resourceName, localPort, remotePort, h.logger) diff --git a/acceptance/framework/vault/helpers.go b/acceptance/framework/vault/helpers.go index 850f8efbed..6ebfb5cf62 100644 --- a/acceptance/framework/vault/helpers.go +++ b/acceptance/framework/vault/helpers.go @@ -33,8 +33,8 @@ func GenerateGossipSecret() (string, error) { func ConfigurePKICerts(t *testing.T, vaultClient *vapi.Client, baseUrl, allowedSubdomain, roleName, ns, datacenter, maxTTL string) string { - allowedDomains := fmt.Sprintf("%s.consul,%s,%s.%s,%s.%s.svc", datacenter, - allowedSubdomain, allowedSubdomain, ns, allowedSubdomain, ns) + allowedDomains := fmt.Sprintf("%s.consul,%s,%s.%s,%s.%s.svc,%s.default.svc.cluster.local", datacenter, + allowedSubdomain, allowedSubdomain, ns, allowedSubdomain, ns, allowedSubdomain) params := map[string]interface{}{ "allowed_domains": allowedDomains, "allow_bare_domains": "true", diff --git a/acceptance/tests/controller/controller_test.go b/acceptance/tests/controller/controller_test.go index 52a78f6fa7..6c8dcbe4e7 100644 --- a/acceptance/tests/controller/controller_test.go +++ b/acceptance/tests/controller/controller_test.go @@ -6,25 +6,42 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/acceptance/framework/config" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul-k8s/acceptance/framework/vault" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/hashicorp/go-uuid" "github.com/stretchr/testify/require" ) +const ( + KubernetesAuthMethodPath = "kubernetes" + ManageSystemACLsRole = "server-acl-init" + ClientRole = "client" + ServerRole = "server" +) + func TestController(t *testing.T) { cfg := suite.Config() cases := []struct { secure bool autoEncrypt bool + useVault bool }{ - {false, false}, - {true, false}, - {true, true}, + {false, false, false}, + {true, false, false}, + {true, true, false}, + {true, true, true}, + {false, false, true}, + // Vault with TLS requires autoEncrypt set to true as well, so the below + // is not valid + // {true, false, true}, } // The name of a service intention in consul is @@ -46,11 +63,22 @@ func TestController(t *testing.T) { } releaseName := helpers.RandomName() + + var bootstrapToken string + var helmConsulValues map[string]string + if c.useVault { + helmConsulValues, bootstrapToken = configureAndGetVaultHelmValues(t, ctx, cfg, releaseName, c.secure) + helpers.MergeMaps(helmConsulValues, helmValues) + } consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) consulCluster.Create(t) consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) + if c.useVault { + consulCluster.ACLToken = bootstrapToken + } + // Test creation. { logger.Log(t, "creating custom resources") @@ -340,3 +368,198 @@ func TestController(t *testing.T) { }) } } + +func configureAndGetVaultHelmValues(t *testing.T, ctx environment.TestContext, + cfg *config.TestConfig, consulReleaseName string, secure bool) (map[string]string, string) { + vaultReleaseName := helpers.RandomName() + ns := ctx.KubectlOptions(t).Namespace + + vaultCluster := vault.NewVaultCluster(t, ctx, cfg, vaultReleaseName, nil) + vaultCluster.Create(t, ctx, "") + // 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", + CommonName: "Consul CA", + } + serverPKIConfig.ConfigurePKIAndAuthRole(t, vaultClient) + + webhookCertTtl := 25 * time.Second + // Configure controller webhook PKI + controllerWebhookPKIConfig := &vault.PKIAndAuthRoleConfiguration{ + BaseURL: "controller", + PolicyName: "controller-ca-policy", + RoleName: "controller-ca-role", + KubernetesNamespace: ns, + DataCenter: "dc1", + ServiceAccountName: fmt.Sprintf("%s-consul-%s", consulReleaseName, "controller"), + AllowedSubdomain: fmt.Sprintf("%s-consul-%s", consulReleaseName, "controller-webhook"), + MaxTTL: webhookCertTtl.String(), + AuthMethodPath: "kubernetes", + } + controllerWebhookPKIConfig.ConfigurePKIAndAuthRole(t, vaultClient) + + // Configure controller webhook PKI + connectInjectorWebhookPKIConfig := &vault.PKIAndAuthRoleConfiguration{ + BaseURL: "connect", + PolicyName: "connect-ca-policy", + RoleName: "connect-ca-role", + KubernetesNamespace: ns, + DataCenter: "dc1", + ServiceAccountName: fmt.Sprintf("%s-consul-%s", consulReleaseName, "connect-injector"), + AllowedSubdomain: fmt.Sprintf("%s-consul-%s", consulReleaseName, "connect-injector"), + MaxTTL: webhookCertTtl.String(), + AuthMethodPath: "kubernetes", + } + connectInjectorWebhookPKIConfig.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 := ServerRole + srvAuthRoleConfig := &vault.KubernetesAuthRoleConfiguration{ + ServiceAccountName: serverPKIConfig.ServiceAccountName, + KubernetesNamespace: ns, + AuthMethodPath: KubernetesAuthMethodPath, + 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: KubernetesAuthMethodPath, + 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: KubernetesAuthMethodPath, + RoleName: manageSystemACLsRole, + PolicyNames: bootstrapTokenSecret.PolicyName, + } + aclAuthRoleConfig.ConfigureK8SAuthRole(t, vaultClient) + + // allow all components to access server ca + srvCAAuthRoleConfig := &vault.KubernetesAuthRoleConfiguration{ + ServiceAccountName: "*", + KubernetesNamespace: ns, + AuthMethodPath: KubernetesAuthMethodPath, + 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", + + "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", + } + + if cfg.EnableEnterprise { + consulHelmValues["global.enterpriseLicense.secretName"] = licenseSecret.Path + consulHelmValues["global.enterpriseLicense.secretKey"] = licenseSecret.Key + } + + if secure { + consulHelmValues["server.serverCert.secretName"] = serverPKIConfig.CertPath + consulHelmValues["global.tls.caCert.secretName"] = serverPKIConfig.CAPath + consulHelmValues["global.secretsBackend.vault.connectInject.tlsCert.secretName"] = connectInjectorWebhookPKIConfig.CertPath + consulHelmValues["global.secretsBackend.vault.connectInject.caCert.secretName"] = connectInjectorWebhookPKIConfig.CAPath + consulHelmValues["global.secretsBackend.vault.controller.tlsCert.secretName"] = controllerWebhookPKIConfig.CertPath + consulHelmValues["global.secretsBackend.vault.controller.caCert.secretName"] = controllerWebhookPKIConfig.CAPath + consulHelmValues["global.secretsBackend.vault.connectInjectRole"] = connectInjectorWebhookPKIConfig.RoleName + consulHelmValues["global.secretsBackend.vault.controllerRole"] = controllerWebhookPKIConfig.RoleName + consulHelmValues["global.acls.bootstrapToken.secretName"] = bootstrapTokenSecret.Path + consulHelmValues["global.acls.bootstrapToken.secretKey"] = bootstrapTokenSecret.Key + consulHelmValues["global.gossipEncryption.secretName"] = gossipSecret.Path + consulHelmValues["global.gossipEncryption.secretKey"] = gossipSecret.Key + } + + return consulHelmValues, bootstrapToken +} diff --git a/acceptance/tests/vault/vault_test.go b/acceptance/tests/vault/vault_test.go index f746f73ed1..49b1b59bf8 100644 --- a/acceptance/tests/vault/vault_test.go +++ b/acceptance/tests/vault/vault_test.go @@ -1,11 +1,14 @@ package vault import ( + "context" "fmt" "testing" + "time" terratestLogger "github.com/gruntwork-io/terratest/modules/logger" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" + "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" @@ -13,6 +16,7 @@ import ( "github.com/hashicorp/go-uuid" "github.com/hashicorp/go-version" "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( @@ -28,7 +32,8 @@ const ( func TestVault(t *testing.T) { cfg := suite.Config() ctx := suite.Environment().DefaultContext(t) - ns := ctx.KubectlOptions(t).Namespace + kubectlOptions := ctx.KubectlOptions(t) + ns := kubectlOptions.Namespace ver, err := version.NewVersion("1.12.0") require.NoError(t, err) @@ -46,6 +51,14 @@ func TestVault(t *testing.T) { // Now fetch the Vault client so we can create the policies and secrets. vaultClient := vaultCluster.VaultClient(t) + // Initially tried to set the expiration to 5-20s to keep the test as short running as possible, + // but at those levels, the pods would fail to start becuase the certs had expired and would throw errors. + // 30s seconds seemed to consistently clear this issue and not have startup problems. + // If trying to go lower, be sure to run this several times in CI to ensure that there are little issues. + // If wanting to make this higher, there is no problem except for consideration of how long the test will + // take to complete. + expirationInSeconds := 30 + // ------------------------- // PKI // ------------------------- @@ -65,11 +78,39 @@ func TestVault(t *testing.T) { DataCenter: "dc1", ServiceAccountName: fmt.Sprintf("%s-consul-%s", consulReleaseName, ServerRole), AllowedSubdomain: fmt.Sprintf("%s-consul-%s", consulReleaseName, ServerRole), - MaxTTL: "1h", + MaxTTL: fmt.Sprintf("%ds", expirationInSeconds), AuthMethodPath: KubernetesAuthMethodPath, } serverPKIConfig.ConfigurePKIAndAuthRole(t, vaultClient) + // Configure controller webhook PKI + controllerWebhookPKIConfig := &vault.PKIAndAuthRoleConfiguration{ + BaseURL: "controller", + PolicyName: "controller-ca-policy", + RoleName: "controller-ca-role", + KubernetesNamespace: ns, + DataCenter: "dc1", + ServiceAccountName: fmt.Sprintf("%s-consul-%s", consulReleaseName, "controller"), + AllowedSubdomain: fmt.Sprintf("%s-consul-%s", consulReleaseName, "controller-webhook"), + MaxTTL: fmt.Sprintf("%ds", expirationInSeconds), + AuthMethodPath: KubernetesAuthMethodPath, + } + controllerWebhookPKIConfig.ConfigurePKIAndAuthRole(t, vaultClient) + + // Configure connect injector webhook PKI + connectInjectorWebhookPKIConfig := &vault.PKIAndAuthRoleConfiguration{ + BaseURL: "connect", + PolicyName: "connect-ca-policy", + RoleName: "connect-ca-role", + KubernetesNamespace: ns, + DataCenter: "dc1", + ServiceAccountName: fmt.Sprintf("%s-consul-%s", consulReleaseName, "connect-injector"), + AllowedSubdomain: fmt.Sprintf("%s-consul-%s", consulReleaseName, "connect-injector"), + MaxTTL: fmt.Sprintf("%ds", expirationInSeconds), + AuthMethodPath: KubernetesAuthMethodPath, + } + connectInjectorWebhookPKIConfig.ConfigurePKIAndAuthRole(t, vaultClient) + // ------------------------- // KV2 secrets // ------------------------- @@ -169,11 +210,17 @@ func TestVault(t *testing.T) { "connectInject.enabled": "true", "connectInject.replicas": "1", "controller.enabled": "true", + "global.secretsBackend.vault.connectInject.tlsCert.secretName": connectInjectorWebhookPKIConfig.CertPath, + "global.secretsBackend.vault.connectInject.caCert.secretName": connectInjectorWebhookPKIConfig.CAPath, + "global.secretsBackend.vault.controller.tlsCert.secretName": controllerWebhookPKIConfig.CertPath, + "global.secretsBackend.vault.controller.caCert.secretName": controllerWebhookPKIConfig.CAPath, "global.secretsBackend.vault.enabled": "true", "global.secretsBackend.vault.consulServerRole": consulServerRole, "global.secretsBackend.vault.consulClientRole": consulClientRole, "global.secretsBackend.vault.consulCARole": serverPKIConfig.RoleName, + "global.secretsBackend.vault.connectInjectRole": connectInjectorWebhookPKIConfig.RoleName, + "global.secretsBackend.vault.controllerRole": controllerWebhookPKIConfig.RoleName, "global.secretsBackend.vault.manageSystemACLsRole": manageSystemACLsRole, "global.secretsBackend.vault.ca.secretName": vaultCASecret, @@ -217,6 +264,21 @@ func TestVault(t *testing.T) { consulCluster := consul.NewHelmCluster(t, consulHelmValues, ctx, cfg, consulReleaseName) consulCluster.Create(t) + // Portforward to connect injector pod and get cert + client := environment.KubernetesClientFromOptions(t, kubectlOptions) + podList, err := client.CoreV1().Pods(kubectlOptions.Namespace).List(context.Background(), + metav1.ListOptions{LabelSelector: fmt.Sprintf("app=consul,component=connect-injector,release=%s", consulReleaseName)}) + require.NoError(t, err) + require.NotEmpty(t, podList.Items) + connectInjectorPodName := podList.Items[0].Name + connectInjectorPodAddress := consulCluster.CreatePortForwardTunnelToResourcePort(t, connectInjectorPodName, 8080) + connectInjectorCert, err := getCertificate(t, connectInjectorPodAddress) + require.NoError(t, err) + logger.Logf(t, "Connect Inject Webhook Cert expiry: %s \n", connectInjectorCert.NotAfter.String()) + + logger.Logf(t, "Wait %d seconds for certificates to rotate....", expirationInSeconds) + time.Sleep(time.Duration(expirationInSeconds) * time.Second) + // Validate that the gossip encryption key is set correctly. logger.Log(t, "Validating the gossip key has been set correctly.") consulCluster.ACLToken = bootstrapToken @@ -266,4 +328,10 @@ func TestVault(t *testing.T) { } else { k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), StaticClientName, "http://localhost:1234") } + + connectInjectorCert2, err := getCertificate(t, connectInjectorPodAddress) + require.NoError(t, err) + // verify that a previous cert expired and that a new one has been issued + // by comparing the NotAfter on the two certs. + require.NotEqual(t, connectInjectorCert.NotAfter, connectInjectorCert2.NotAfter) } diff --git a/acceptance/tests/vault/vault_tls_auto_reload_test.go b/acceptance/tests/vault/vault_tls_auto_reload_test.go index 371e0428d5..6cbcb5d351 100644 --- a/acceptance/tests/vault/vault_tls_auto_reload_test.go +++ b/acceptance/tests/vault/vault_tls_auto_reload_test.go @@ -21,7 +21,7 @@ import ( // TestVault_TlsAutoReload installs Vault, bootstraps it with secrets, policies, and Kube Auth Method. // It then gets certs for https and rpc on the server. It then waits for the certs to rotate and checks // that certs have different expirations. -func TestVault_TlsAutoReload(t *testing.T) { +func TestVault_TLSAutoReload(t *testing.T) { cfg := suite.Config() ctx := suite.Environment().DefaultContext(t) ns := ctx.KubectlOptions(t).Namespace diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index b95975ab91..56f4fbf128 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -22,13 +22,17 @@ as well as the global.name setting. {{ "{{" }}- end -{{ "}}" }} {{- end -}} -{{- define "consul.serverTLSCATemplate" -}} +{{- define "consul.vaultCATemplate" -}} | - {{ "{{" }}- with secret "{{ .Values.global.tls.caCert.secretName }}" -{{ "}}" }} + {{ "{{" }}- with secret "{{ .secretName }}" -{{ "}}" }} {{ "{{" }}- .Data.certificate -{{ "}}" }} {{ "{{" }}- end -{{ "}}" }} {{- end -}} +{{- define "consul.serverTLSCATemplate" -}} +{{ include "consul.vaultCATemplate" .Values.global.tls.caCert }} +{{- end -}} + {{- define "consul.serverTLSCertTemplate" -}} | {{ "{{" }}- with secret "{{ .Values.server.serverCert.secretName }}" "{{ printf "common_name=server.%s.%s" .Values.global.datacenter .Values.global.domain }}" @@ -53,6 +57,38 @@ as well as the global.name setting. {{ "{{" }}- end -{{ "}}" }} {{- end -}} +{{- define "consul.connectInjectWebhookTLSCertTemplate" -}} + | + {{ "{{" }}- with secret "{{ .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName }}" "{{- $name := include "consul.fullname" . -}}{{ printf "common_name=%s-connect-injector" $name }}" + "alt_names={{ include "consul.connectInjectorTLSAltNames" . }}" -{{ "}}" }} + {{ "{{" }}- .Data.certificate -{{ "}}" }} + {{ "{{" }}- end -{{ "}}" }} +{{- end -}} + +{{- define "consul.connectInjectWebhookTLSKeyTemplate" -}} + | + {{ "{{" }}- with secret "{{ .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName }}" "{{- $name := include "consul.fullname" . -}}{{ printf "common_name=%s-connect-injector" $name }}" + "alt_names={{ include "consul.connectInjectorTLSAltNames" . }}" -{{ "}}" }} + {{ "{{" }}- .Data.private_key -{{ "}}" }} + {{ "{{" }}- end -{{ "}}" }} +{{- end -}} + +{{- define "consul.controllerWebhookTLSCertTemplate" -}} + | + {{ "{{" }}- with secret "{{ .Values.global.secretsBackend.vault.controller.tlsCert.secretName }}" "{{- $name := include "consul.fullname" . -}}{{ printf "common_name=%s-controller-webhook" $name }}" + "alt_names={{ include "consul.controllerWebhookTLSAltNames" . }}" -{{ "}}" }} + {{ "{{" }}- .Data.certificate -{{ "}}" }} + {{ "{{" }}- end -{{ "}}" }} +{{- end -}} + +{{- define "consul.controllerWebhookTLSKeyTemplate" -}} + | + {{ "{{" }}- with secret "{{ .Values.global.secretsBackend.vault.controller.tlsCert.secretName }}" "{{- $name := include "consul.fullname" . -}}{{ printf "common_name=%s-controller-webhook" $name }}" + "alt_names={{ include "consul.controllerWebhookTLSAltNames" . }}" -{{ "}}" }} + {{ "{{" }}- .Data.private_key -{{ "}}" }} + {{ "{{" }}- end -{{ "}}" }} +{{- end -}} + {{- define "consul.serverTLSAltNames" -}} {{- $name := include "consul.fullname" . -}} {{- $ns := .Release.Namespace -}} @@ -64,7 +100,19 @@ as well as the global.name setting. {{- end -}} {{- define "consul.serverAdditionalIPSANs" -}} -{{- if .Values.global.tls -}}{{- if .Values.global.tls.serverAdditionalIPSANs -}}{{- range $ipsan := .Values.global.tls.serverAdditionalIPSANs }},{{ $ipsan }} {{- end -}}{{- end -}}{{- end -}} +{{- if .Values.global.tls -}}{{- if .Values.global.tls.serverAdditionalIPSANs -}}{{- range $san := .Values.global.tls.serverAdditionalIPSANs }},{{ $san }} {{- end -}}{{- end -}}{{- end -}} +{{- end -}} + +{{- define "consul.connectInjectorTLSAltNames" -}} +{{- $name := include "consul.fullname" . -}} +{{- $ns := .Release.Namespace -}} +{{ printf "%s-connect-injector,%s-connect-injector.%s,%s-connect-injector.%s.svc,%s-connect-injector.%s.svc.cluster.local" $name $name $ns $name $ns $name $ns}} +{{- end -}} + +{{- define "consul.controllerWebhookTLSAltNames" -}} +{{- $name := include "consul.fullname" . -}} +{{- $ns := .Release.Namespace -}} +{{ printf "%s-controller-webhook,%s-controller-webhook.%s,%s-controller-webhook.%s.svc,%s-controller-webhook.%s.svc.cluster.local" $name $name $ns $name $ns $name $ns}} {{- end -}} {{- define "consul.vaultReplicationTokenTemplate" -}} @@ -228,3 +276,26 @@ Usage: {{ template "consul.reservedNamesFailer" (list .Values.key "key") }} {{- fail (cat "The name" $name "set for key" $key "is reserved by Consul for future use." ) }} {{- end }} {{- end -}} + +{{/* +Fails when at least one but not all of the following have been set: +- global.secretsBackend.vault.connectInjectRole +- global.secretsBackend.vault.connectInject.tlsCert.secretName +- global.secretsBackend.vault.connectInject.caCert.secretName +- global.secretsBackend.vault.controllerRole +- global.secretsBackend.vault.controller.tlsCert.secretName +- global.secretsBackend.vault.controller.caCert.secretName + +The above values are needed in full to turn off web cert manager and allow +connect inject and controller to manage its own webhook certs. + +Usage: {{ template "consul.validateVaultWebhookCertConfiguration" . }} + +*/}} +{{- define "consul.validateVaultWebhookCertConfiguration" -}} +{{- if or .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName}} +{{- if or (not .Values.global.secretsBackend.vault.connectInjectRole) (not .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName) (not .Values.global.secretsBackend.vault.connectInject.caCert.secretName) (not .Values.global.secretsBackend.vault.controllerRole) (not .Values.global.secretsBackend.vault.controller.tlsCert.secretName) (not .Values.global.secretsBackend.vault.controller.caCert.secretName) }} +{{fail "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName."}} +{{ end }} +{{ end }} +{{- end -}} diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index 683a9c6bf7..9018320dd2 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -33,6 +33,17 @@ rules: - get - list - update +{{- if (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName)}} +- apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + verbs: + - get + - list + - watch + - patch +{{- end }} {{- if .Values.global.enablePodSecurityPolicies }} - apiGroups: [ "policy" ] resources: [ "podsecuritypolicies" ] diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index 9865ff0b01..65e0c1d87e 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -8,6 +8,7 @@ {{- if .Values.connectInject.centralConfig }}{{ if .Values.connectInject.centralConfig.proxyDefaults }}{{- if ne (trim .Values.connectInject.centralConfig.proxyDefaults) `{}` }}{{ fail "connectInject.centralConfig.proxyDefaults is no longer supported; instead you must migrate to CRDs (see www.consul.io/docs/k8s/crds/upgrade-to-crds)" }}{{ end }}{{ end }}{{ end -}} {{- if .Values.connectInject.imageEnvoy }}{{ fail "connectInject.imageEnvoy must be specified in global.imageEnvoy" }}{{ end }} {{- if .Values.global.lifecycleSidecarContainer }}{{ fail "global.lifecycleSidecarContainer has been renamed to global.consulSidecarContainer. Please set values using global.consulSidecarContainer." }}{{ end }} +{{ template "consul.validateVaultWebhookCertConfiguration" . }} {{- template "consul.reservedNamesFailer" (list .Values.connectInject.consulNamespaces.consulDestinationNamespace "connectInject.consulNamespaces.consulDestinationNamespace") }} # The deployment for running the Connect sidecar injector apiVersion: apps/v1 @@ -41,9 +42,28 @@ spec: {{- if (and .Values.global.secretsBackend.vault.enabled .Values.global.tls.enabled) }} "vault.hashicorp.com/agent-init-first": "true" "vault.hashicorp.com/agent-inject": "true" + {{- if .Values.global.secretsBackend.vault.connectInjectRole }} + "vault.hashicorp.com/role": {{ .Values.global.secretsBackend.vault.connectInjectRole }} + {{ else }} "vault.hashicorp.com/role": {{ .Values.global.secretsBackend.vault.consulCARole }} + {{ end }} "vault.hashicorp.com/agent-inject-secret-serverca.crt": {{ .Values.global.tls.caCert.secretName }} "vault.hashicorp.com/agent-inject-template-serverca.crt": {{ template "consul.serverTLSCATemplate" . }} + {{- if .Values.global.secretsBackend.vault.connectInject.caCert.secretName }} + {{- with .Values.global.secretsBackend.vault.connectInject.caCert }} + "vault.hashicorp.com/agent-inject-secret-ca.crt": {{ .secretName }} + "vault.hashicorp.com/agent-inject-template-ca.crt": {{ template "consul.vaultCATemplate" . }} + "vault.hashicorp.com/secret-volume-path-ca.crt": "/vault/secrets/connect-injector/certs" + {{- end }} + {{- end }} + {{- if .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName }} + "vault.hashicorp.com/agent-inject-secret-tls.crt": {{ .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName }} + "vault.hashicorp.com/agent-inject-template-tls.crt": {{ include "consul.connectInjectWebhookTLSCertTemplate" . }} + "vault.hashicorp.com/secret-volume-path-tls.crt": "/vault/secrets/connect-injector/certs" + "vault.hashicorp.com/agent-inject-secret-tls.key": {{ .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName }} + "vault.hashicorp.com/agent-inject-template-tls.key": {{ include "consul.connectInjectWebhookTLSKeyTemplate" . }} + "vault.hashicorp.com/secret-volume-path-tls.key": "/vault/secrets/connect-injector/certs" + {{- end }} {{- if and .Values.global.secretsBackend.vault.ca.secretName .Values.global.secretsBackend.vault.ca.secretKey }} "vault.hashicorp.com/agent-extra-secret": "{{ .Values.global.secretsBackend.vault.ca.secretName }}" "vault.hashicorp.com/ca-cert": "/vault/custom/{{ .Values.global.secretsBackend.vault.ca.secretKey }}" @@ -168,7 +188,12 @@ spec: -consul-cross-namespace-acl-policy=cross-namespace-policy \ {{- end }} {{- end }} + {{- if and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName }} + -tls-cert-dir=/vault/secrets/connect-injector/certs \ + -enable-webhook-ca-update \ + {{- else }} -tls-cert-dir=/etc/connect-injector/certs \ + {{- end }} {{- $resources := .Values.connectInject.sidecarProxy.resources }} {{- /* kindIs is used here to differentiate between null and 0 */}} {{- if not (kindIs "invalid" $resources.limits.memory) }} @@ -252,9 +277,11 @@ spec: successThreshold: 1 timeoutSeconds: 5 volumeMounts: + {{- if not (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName) }} - name: certs mountPath: /etc/connect-injector/certs readOnly: true + {{- end }} - mountPath: /consul/login name: consul-data readOnly: true @@ -272,10 +299,12 @@ spec: {{- toYaml . | nindent 12 }} {{- end }} volumes: + {{- if not (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName) }} - name: certs secret: defaultMode: 420 secretName: {{ template "consul.fullname" . }}-connect-inject-webhook-cert + {{- end }} - name: consul-data emptyDir: medium: "Memory" diff --git a/charts/consul/templates/controller-clusterrole.yaml b/charts/consul/templates/controller-clusterrole.yaml index e2522a2eae..fc0753cc06 100644 --- a/charts/consul/templates/controller-clusterrole.yaml +++ b/charts/consul/templates/controller-clusterrole.yaml @@ -57,6 +57,17 @@ rules: - get - list - update +{{- if (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName)}} +- apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + verbs: + - get + - list + - watch + - patch +{{- end }} {{- if .Values.global.enablePodSecurityPolicies }} - apiGroups: ["policy"] resources: ["podsecuritypolicies"] diff --git a/charts/consul/templates/controller-deployment.yaml b/charts/consul/templates/controller-deployment.yaml index de0b0107d7..44b13553bc 100644 --- a/charts/consul/templates/controller-deployment.yaml +++ b/charts/consul/templates/controller-deployment.yaml @@ -1,5 +1,6 @@ {{- if .Values.controller.enabled }} {{- if and .Values.global.adminPartitions.enabled (not .Values.global.enableConsulNamespaces) }}{{ fail "global.enableConsulNamespaces must be true if global.adminPartitions.enabled=true" }}{{ end }} +{{ template "consul.validateVaultWebhookCertConfiguration" . }} apiVersion: apps/v1 kind: Deployment metadata: @@ -33,9 +34,28 @@ spec: {{- if (and .Values.global.secretsBackend.vault.enabled .Values.global.tls.enabled) }} "vault.hashicorp.com/agent-init-first": "true" "vault.hashicorp.com/agent-inject": "true" + {{- if .Values.global.secretsBackend.vault.controllerRole }} + "vault.hashicorp.com/role": {{ .Values.global.secretsBackend.vault.controllerRole }} + {{ else }} "vault.hashicorp.com/role": {{ .Values.global.secretsBackend.vault.consulCARole }} + {{ end }} "vault.hashicorp.com/agent-inject-secret-serverca.crt": {{ .Values.global.tls.caCert.secretName }} "vault.hashicorp.com/agent-inject-template-serverca.crt": {{ template "consul.serverTLSCATemplate" . }} + {{- if .Values.global.secretsBackend.vault.controller.caCert.secretName }} + {{- with .Values.global.secretsBackend.vault.controller.caCert }} + "vault.hashicorp.com/agent-inject-secret-ca.crt": {{ .secretName }} + "vault.hashicorp.com/agent-inject-template-ca.crt": {{ template "consul.vaultCATemplate" . }} + "vault.hashicorp.com/secret-volume-path-ca.crt": "/vault/secrets/controller-webhook/certs" + {{- end }} + {{- end }} + {{- if .Values.global.secretsBackend.vault.controller.tlsCert.secretName }} + "vault.hashicorp.com/agent-inject-secret-tls.crt": {{ .Values.global.secretsBackend.vault.controller.tlsCert.secretName }} + "vault.hashicorp.com/agent-inject-template-tls.crt": {{ include "consul.controllerWebhookTLSCertTemplate" . }} + "vault.hashicorp.com/secret-volume-path-tls.crt": "/vault/secrets/controller-webhook/certs" + "vault.hashicorp.com/agent-inject-secret-tls.key": {{ .Values.global.secretsBackend.vault.controller.tlsCert.secretName }} + "vault.hashicorp.com/agent-inject-template-tls.key": {{ include "consul.controllerWebhookTLSKeyTemplate" . }} + "vault.hashicorp.com/secret-volume-path-tls.key": "/vault/secrets/controller-webhook/certs" + {{- end }} {{- if and .Values.global.secretsBackend.vault.ca.secretName .Values.global.secretsBackend.vault.ca.secretKey }} "vault.hashicorp.com/agent-extra-secret": "{{ .Values.global.secretsBackend.vault.ca.secretName }}" "vault.hashicorp.com/ca-cert": "/vault/custom/{{ .Values.global.secretsBackend.vault.ca.secretKey }}" @@ -117,7 +137,13 @@ spec: -consul-api-timeout={{ .Values.global.consulAPITimeout }} \ -log-level={{ default .Values.global.logLevel .Values.controller.logLevel }} \ -log-json={{ .Values.global.logJSON }} \ + -resource-prefix={{ template "consul.fullname" . }} \ + {{- if and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.controller.tlsCert.secretName }} + -enable-webhook-ca-update \ + -webhook-tls-cert-dir=/vault/secrets/controller-webhook/certs \ + {{- else }} -webhook-tls-cert-dir=/tmp/controller-webhook/certs \ + {{- end }} -datacenter={{ .Values.global.datacenter }} \ {{- if .Values.global.adminPartitions.enabled }} -partition={{ .Values.global.adminPartitions.name }} \ @@ -188,9 +214,11 @@ spec: - mountPath: /consul/login name: consul-data readOnly: true + {{- if not (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.controller.tlsCert.secretName) }} - mountPath: /tmp/controller-webhook/certs name: cert readOnly: true + {{- end }} {{- if .Values.global.tls.enabled }} {{- if .Values.global.tls.enableAutoEncrypt }} - name: consul-auto-encrypt-ca-cert @@ -202,10 +230,12 @@ spec: {{- end }} terminationGracePeriodSeconds: 10 volumes: + {{- if not (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.controller.tlsCert.secretName) }} - name: cert secret: defaultMode: 420 secretName: {{ template "consul.fullname" . }}-controller-webhook-cert + {{- end }} {{- if .Values.global.tls.enabled }} {{- if not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) }} - name: consul-ca-cert diff --git a/charts/consul/templates/webhook-cert-manager-clusterrole.yaml b/charts/consul/templates/webhook-cert-manager-clusterrole.yaml index eb812035f3..ce8dfb846c 100644 --- a/charts/consul/templates/webhook-cert-manager-clusterrole.yaml +++ b/charts/consul/templates/webhook-cert-manager-clusterrole.yaml @@ -1,4 +1,5 @@ -{{- if or .Values.connectInject.enabled .Values.controller.enabled}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} +{{- if (and (or .Values.connectInject.enabled .Values.controller.enabled) (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: diff --git a/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml b/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml index 910c4bf84d..90192d5966 100644 --- a/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml +++ b/charts/consul/templates/webhook-cert-manager-clusterrolebinding.yaml @@ -1,4 +1,5 @@ -{{- if or .Values.connectInject.enabled .Values.controller.enabled}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} +{{- if (and (or .Values.connectInject.enabled .Values.controller.enabled) (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: diff --git a/charts/consul/templates/webhook-cert-manager-configmap.yaml b/charts/consul/templates/webhook-cert-manager-configmap.yaml index e13d14a7ab..61520fe230 100644 --- a/charts/consul/templates/webhook-cert-manager-configmap.yaml +++ b/charts/consul/templates/webhook-cert-manager-configmap.yaml @@ -1,4 +1,5 @@ -{{- if or .Values.connectInject.enabled .Values.controller.enabled}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} +{{- if (and (or .Values.connectInject.enabled .Values.controller.enabled) (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: v1 kind: ConfigMap metadata: @@ -39,4 +40,4 @@ data: } {{- end }} ] - {{- end }} \ No newline at end of file + {{- end }} diff --git a/charts/consul/templates/webhook-cert-manager-deployment.yaml b/charts/consul/templates/webhook-cert-manager-deployment.yaml index 9974c4c1cd..609f3314b3 100644 --- a/charts/consul/templates/webhook-cert-manager-deployment.yaml +++ b/charts/consul/templates/webhook-cert-manager-deployment.yaml @@ -1,4 +1,5 @@ -{{- if or .Values.connectInject.enabled .Values.controller.enabled}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} +{{- if (and (or .Values.connectInject.enabled .Values.controller.enabled) (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: apps/v1 kind: Deployment metadata: diff --git a/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml b/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml index 1cc626d6e0..833d902343 100644 --- a/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml +++ b/charts/consul/templates/webhook-cert-manager-podsecuritypolicy.yaml @@ -1,4 +1,5 @@ -{{- if and (or .Values.controller.enabled .Values.connectInject.enabled) .Values.global.enablePodSecurityPolicies }} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} +{{- if (and (or .Values.connectInject.enabled .Values.controller.enabled) .Values.global.enablePodSecurityPolicies (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: diff --git a/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml b/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml index 3e880434dd..e1680d6e50 100644 --- a/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml +++ b/charts/consul/templates/webhook-cert-manager-serviceaccount.yaml @@ -1,4 +1,5 @@ -{{- if or .Values.connectInject.enabled .Values.controller.enabled}} +{{ $hasConfiguredWebhookCertsUsingVault := (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInjectRole .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName .Values.global.secretsBackend.vault.connectInject.caCert.secretName .Values.global.secretsBackend.vault.controllerRole .Values.global.secretsBackend.vault.controller.tlsCert.secretName .Values.global.secretsBackend.vault.controller.caCert.secretName) -}} +{{- if (and (or .Values.connectInject.enabled .Values.controller.enabled) (not $hasConfiguredWebhookCertsUsingVault)) }} apiVersion: v1 kind: ServiceAccount metadata: diff --git a/charts/consul/test/unit/connect-inject-clusterrole.bats b/charts/consul/test/unit/connect-inject-clusterrole.bats index e954b8908a..7939755f81 100644 --- a/charts/consul/test/unit/connect-inject-clusterrole.bats +++ b/charts/consul/test/unit/connect-inject-clusterrole.bats @@ -29,6 +29,94 @@ load _helpers . } +#-------------------------------------------------------------------- +# rules + +@test "connectInject/ClusterRole: sets get, list, and watch access to pods, endpoints, services, and namespaces in all api groups" { + cd `chart_dir` + local object=$(helm template \ + -s templates/connect-inject-clusterrole.yaml \ + --set 'global.enabled=false' \ + --set 'client.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.rules[0]' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.resources[| index("pods")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources[| index("endpoints")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources[| index("services")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources[| index("namespaces")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.apiGroups[0]' | tee /dev/stderr) + [ "${actual}" = "" ] + + local actual=$(echo $object | yq -r '.verbs | index("get")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("list")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("watch")' | tee /dev/stderr) + [ "${actual}" != null ] +} + +@test "connectInject/ClusterRole: sets create, get, list, and update access to leases in the coordination.k8s.io api group" { + cd `chart_dir` + local object=$(helm template \ + -s templates/connect-inject-clusterrole.yaml \ + --set 'global.enabled=false' \ + --set 'client.enabled=true' \ + --set 'connectInject.enabled=true' \ + . | tee /dev/stderr | + yq -r '.rules[1]' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.resources[| index("leases")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.apiGroups[0]' | tee /dev/stderr) + [ "${actual}" = "coordination.k8s.io" ] + + local actual=$(echo $object | yq -r '.verbs | index("create")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("get")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("list")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("update")' | tee /dev/stderr) + [ "${actual}" != null ] +} + +@test "connectInject/ClusterRole: sets get access to serviceaccounts when manageSystemACLSis true" { + cd `chart_dir` + local object=$(helm template \ + -s templates/connect-inject-clusterrole.yaml \ + --set 'global.enabled=false' \ + --set 'client.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'global.acls.manageSystemACLs=true' \ + . | tee /dev/stderr | + yq -r '.rules[0]' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.resources[| index("serviceaccounts")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.apiGroups[0]' | tee /dev/stderr) + [ "${actual}" = "" ] + + local actual=$(echo $object | yq -r '.verbs | index("get")' | tee /dev/stderr) + [ "${actual}" != null ] +} + #-------------------------------------------------------------------- # global.enablePodSecurityPolicies @@ -53,3 +141,48 @@ load _helpers yq -r '.rules | map(select(.resources[0] == "podsecuritypolicies")) | length' | tee /dev/stderr) [ "${actual}" = "1" ] } + +#-------------------------------------------------------------------- +# vault + +@test "connectInject/ClusterRole: vault sets get, list, watch, and patch access to mutatingwebhookconfigurations when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, and global.secretsBackend.vault.connectInject.caCert.secretName." { + cd `chart_dir` + local object=$(helm template \ + -s templates/connect-inject-clusterrole.yaml \ + --set 'global.enabled=false' \ + --set 'client.enabled=true' \ + --set 'connectInject.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + . | tee /dev/stderr | + yq -r '.rules[2]' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.resources[0]' | tee /dev/stderr) + [ "${actual}" = "mutatingwebhookconfigurations" ] + + local actual=$(echo $object | yq -r '.apiGroups[0]' | tee /dev/stderr) + [ "${actual}" = "admissionregistration.k8s.io" ] + + local actual=$(echo $object | yq -r '.verbs | index("get")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("list")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("patch")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("watch")' | tee /dev/stderr) + [ "${actual}" != null ] +} diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index 5dfa8d6d89..552fe72c21 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -617,6 +617,17 @@ EOF [ "${actual}" = "key" ] } +@test "connectInject/Deployment: Adds -tls-cert-dir=/etc/connect-injector/certs to command" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-tls-cert-dir=/etc/connect-injector/certs"))' | tee /dev/stderr) + [ "${actual}" != "" ] +} + #-------------------------------------------------------------------- # global.tls.enableAutoEncrypt @@ -1813,6 +1824,46 @@ EOF [ "${actual}" = "true" ] } +#-------------------------------------------------------------------- +# enable-webhook-ca-update + +@test "connectInject/Deployment: enable-webhook-ca-update flag is not set on command by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.tls.caCert.secretName=foo' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-enable-webhook-ca-update"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "connectInject/Deployment: enable-webhook-ca-update flag is not set on command when using vault" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=test' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + --set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \ + --set 'global.secretsBackend.vault.connectInjectRole=test' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-enable-webhook-ca-update"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + #-------------------------------------------------------------------- # Vault @@ -1827,7 +1878,7 @@ EOF --set 'global.secretsBackend.vault.enabled=true' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=test' \ - --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ . | tee /dev/stderr | yq -r '.spec.template' | tee /dev/stderr) @@ -1848,7 +1899,7 @@ EOF --set 'global.secretsBackend.vault.enabled=true' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=test' \ - --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ --set 'global.secretsBackend.vault.ca.secretName=ca' \ . | tee /dev/stderr | yq -r '.spec.template' | tee /dev/stderr) @@ -1870,7 +1921,7 @@ EOF --set 'global.secretsBackend.vault.enabled=true' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=test' \ - --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ --set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \ . | tee /dev/stderr | yq -r '.spec.template' | tee /dev/stderr) @@ -1892,7 +1943,7 @@ EOF --set 'global.secretsBackend.vault.enabled=true' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=test' \ - --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ --set 'global.secretsBackend.vault.ca.secretName=ca' \ --set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \ . | tee /dev/stderr | @@ -1904,6 +1955,60 @@ EOF [ "${actual}" = "/vault/custom/tls.crt" ] } +@test "connectInject/Deployment: fails if vault is enabled and global.secretsBackend.vault.connectInjectRole is set but global.secretsBackend.vault.connectInject.tlsCert.secretName and global.secretsBackend.vault.connectInject.caCert.secretName are not" { + cd `chart_dir` + run helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.connectInjectRole=connectinjectcarole' \ + --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' . + [ "$status" -eq 1 ] + [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName." ]] +} + +@test "connectInject/Deployment: fails if vault is enabled and global.secretsBackend.vault.connectInject.tlsCert.secretName is set but global.secretsBackend.vault.connectInjectRole and global.secretsBackend.vault.connectInject.caCert.secretName are not" { + cd `chart_dir` + run helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=connectInject/Deployment: enable-webhook-ca-update flag is not set on command when using vaulttest' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' . + [ "$status" -eq 1 ] + [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName." ]] +} + +@test "connectInject/Deployment: fails if vault is enabled and global.secretsBackend.vault.connectInject.caCert.secretName is set but global.secretsBackend.vault.connectInjectRole and global.secretsBackend.vault.connectInject.tlsCert.secretName are not" { + cd `chart_dir` + run helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' . + [ "$status" -eq 1 ] + [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName." ]] +} + @test "connectInject/Deployment: vault tls annotations are set when tls is enabled" { cd `chart_dir` local cmd=$(helm template \ @@ -1912,11 +2017,17 @@ EOF --set 'global.secretsBackend.vault.enabled=true' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ - --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ --set 'global.tls.enabled=true' \ --set 'global.tls.enableAutoEncrypt=true' \ --set 'server.serverCert.secretName=pki_int/issue/test' \ --set 'global.tls.caCert.secretName=pki_int/cert/ca' \ + --set 'global.secretsBackend.vault.connectInjectRole=test' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ . | tee /dev/stderr | yq -r '.spec.template.metadata' | tee /dev/stderr) @@ -1929,6 +2040,19 @@ EOF yq -r '.annotations["vault.hashicorp.com/agent-inject-secret-serverca.crt"]' | tee /dev/stderr)" [ "${actual}" = "pki_int/cert/ca" ] + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-template-ca.crt"]' | tee /dev/stderr)" + local expected=$'{{- with secret \"foo/ca\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' + [ "${actual}" = "${expected}" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-secret-ca.crt"]' | tee /dev/stderr)" + [ "${actual}" = "foo/ca" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/secret-volume-path-ca.crt"]' | tee /dev/stderr)" + [ "${actual}" = "/vault/secrets/connect-injector/certs" ] + local actual="$(echo $cmd | yq -r '.annotations["vault.hashicorp.com/agent-init-first"]' | tee /dev/stderr)" [ "${actual}" = "true" ] @@ -1940,6 +2064,151 @@ EOF local actual="$(echo $cmd | yq -r '.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr)" [ "${actual}" = "test" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-secret-tls.crt"]' | tee /dev/stderr)" + [ "${actual}" = "pki/issue/connect-webhook-cert-dc1" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-template-tls.crt"]' | tee /dev/stderr)" + local expected=$'{{- with secret \"pki/issue/connect-webhook-cert-dc1\" \"common_name=release-name-consul-connect-injector\"\n\"alt_names=release-name-consul-connect-injector,release-name-consul-connect-injector.default,release-name-consul-connect-injector.default.svc,release-name-consul-connect-injector.default.svc.cluster.local\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' + [ "${actual}" = "${expected}" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/secret-volume-path-tls.crt"]' | tee /dev/stderr)" + [ "${actual}" = "/vault/secrets/connect-injector/certs" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-secret-tls.key"]' | tee /dev/stderr)" + [ "${actual}" = "pki/issue/connect-webhook-cert-dc1" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-template-tls.key"]' | tee /dev/stderr)" + local expected=$'{{- with secret \"pki/issue/connect-webhook-cert-dc1\" \"common_name=release-name-consul-connect-injector\"\n\"alt_names=release-name-consul-connect-injector,release-name-consul-connect-injector.default,release-name-consul-connect-injector.default.svc,release-name-consul-connect-injector.default.svc.cluster.local\" -}}\n{{- .Data.private_key -}}\n{{- end -}}' + [ "${actual}" = "${expected}" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/secret-volume-path-tls.key"]' | tee /dev/stderr)" + [ "${actual}" = "/vault/secrets/connect-injector/certs" ] +} + +@test "connectInject/Deployment: vault tls-cert-dir flag is set to /vault/secrets/connect-injector/certs" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-tls-cert-dir=/vault/secrets/connect-injector/certs"))' | tee /dev/stderr) + + [ "${actual}" = "true" ] +} + +@test "connectInject/Deployment: vault ca annotations are set when tls is enabled" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + --set 'global.tls.enabled=true' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'server.serverCert.secretName=pki_int/issue/test' \ + --set 'global.tls.caCert.secretName=pki_int/cert/ca' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata' | tee /dev/stderr) + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-template-serverca.crt"]' | tee /dev/stderr)" + local expected=$'{{- with secret \"pki_int/cert/ca\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' + [ "${actual}" = "${expected}" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-secret-serverca.crt"]' | tee /dev/stderr)" + [ "${actual}" = "pki_int/cert/ca" ] +} + +@test "connectInject/Deployment: vault does not add certs volume when global.secretsBackend.vault.connectInject.tlsCert.secretName is set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + . | tee /dev/stderr | + yq '.spec.template.spec.volumes[] | select(.name == "certs")' | tee /dev/stderr) + [ "${actual}" == "" ] +} + +@test "connectInject/Deployment: vault does not add certs volumeMounts when global.secretsBackend.vault.connectInject.tlsCert.secretName is set" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + --set 'connectInject.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "certs")' | tee /dev/stderr) + [ "${actual}" == "" ] +} + +@test "connectInject/Deployment: vault vault.hashicorp.com/role set to global.secretsBackend.vault.consulCARole if global.secretsBackend.vault.connectInjectRole is not set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/connect-inject-deployment.yaml \ + --set 'connectInject.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata' | tee /dev/stderr) + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr)" + [ "${actual}" = "carole" ] } #-------------------------------------------------------------------- diff --git a/charts/consul/test/unit/controller-clusterrole.bats b/charts/consul/test/unit/controller-clusterrole.bats index dc0b560e1d..708d32d6be 100644 --- a/charts/consul/test/unit/controller-clusterrole.bats +++ b/charts/consul/test/unit/controller-clusterrole.bats @@ -18,6 +18,149 @@ load _helpers yq 'length > 0' | tee /dev/stderr) [ "${actual}" = "true" ] } +#-------------------------------------------------------------------- +# rules + +@test "controller/ClusterRole: sets create, delete, get, list, patch, update and watch access to all CRDs in the consul.hashicorp.com api group" { + cd `chart_dir` + local object=$(helm template \ + -s templates/controller-clusterrole.yaml \ + --set 'controller.enabled=true' \ + . | tee /dev/stderr | + yq -r '.rules[0]' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.apiGroups[0]' | tee /dev/stderr) + [ "${actual}" = "consul.hashicorp.com" ] + + local actual=$(echo $object | yq -r '.resources | index("servicedefaults")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("serviceresolvers")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("proxydefaults")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("meshes")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("exportedservices")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("servicerouters")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("servicesplitters")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("serviceintentions")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("ingressgateways")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("terminatinggateways")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("create")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("delete")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("get")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("list")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("patch")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("update")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("watch")' | tee /dev/stderr) + [ "${actual}" != null ] +} + +@test "controller/ClusterRole: sets get, patch, and update to all CRDs status in the consul.hashicorp.com api group" { + cd `chart_dir` + local object=$(helm template \ + -s templates/controller-clusterrole.yaml \ + --set 'controller.enabled=true' \ + . | tee /dev/stderr | + yq -r '.rules[1]' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.apiGroups[0]' | tee /dev/stderr) + [ "${actual}" = "consul.hashicorp.com" ] + + local actual=$(echo $object | yq -r '.resources | index("servicedefaults/status")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("serviceresolvers/status")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("proxydefaults/status")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("meshes/status")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("exportedservices/status")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("servicerouters/status")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("servicesplitters/status")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("serviceintentions/status")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("ingressgateways/status")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.resources | index("terminatinggateways/status")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("get")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("patch")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("update")' | tee /dev/stderr) + [ "${actual}" != null ] +} + +@test "controller/ClusterRole: sets create, get, list, and update access to leases in the coordination.k8s.io api group" { + cd `chart_dir` + local object=$(helm template \ + -s templates/controller-clusterrole.yaml \ + --set 'controller.enabled=true' \ + . | tee /dev/stderr | + yq -r '.rules[2]' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.resources[| index("leases")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.apiGroups[0]' | tee /dev/stderr) + [ "${actual}" = "coordination.k8s.io" ] + + local actual=$(echo $object | yq -r '.verbs | index("create")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("get")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("list")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("update")' | tee /dev/stderr) + [ "${actual}" != null ] +} #-------------------------------------------------------------------- # global.enablePodSecurityPolicies @@ -43,3 +186,46 @@ load _helpers yq '.rules | map(select(.resources[0] == "podsecuritypolicies")) | length' | tee /dev/stderr) [ "${actual}" = "1" ] } + +#-------------------------------------------------------------------- +# vault + +@test "controller/ClusterRole: vault sets get, list, watch, and patch access to mutatingwebhookconfigurations when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName." { + cd `chart_dir` + local object=$(helm template \ + -s templates/controller-clusterrole.yaml \ + --set 'controller.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + . | tee /dev/stderr | + yq -r '.rules[3]' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.resources[0]' | tee /dev/stderr) + [ "${actual}" = "mutatingwebhookconfigurations" ] + + local actual=$(echo $object | yq -r '.apiGroups[0]' | tee /dev/stderr) + [ "${actual}" = "admissionregistration.k8s.io" ] + + local actual=$(echo $object | yq -r '.verbs | index("get")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("list")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("patch")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("watch")' | tee /dev/stderr) + [ "${actual}" != null ] +} diff --git a/charts/consul/test/unit/controller-deployment.bats b/charts/consul/test/unit/controller-deployment.bats index 28e9e46ca5..87bb98b1f9 100644 --- a/charts/consul/test/unit/controller-deployment.bats +++ b/charts/consul/test/unit/controller-deployment.bats @@ -36,6 +36,59 @@ load _helpers [ "${actual}" = "true" ] } +#-------------------------------------------------------------------- +# resourcePrefix + +@test "controller/Deployment: resource-prefix flag is set on command" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/controller-deployment.yaml \ + --set 'controller.enabled=true' \ + --set 'global.enableConsulNamespaces=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-resource-prefix=release-name-consul"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + +#-------------------------------------------------------------------- +# enable-webhook-ca-update + +@test "controller/Deployment: enable-webhook-ca-update flag is not set on command by default" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/controller-deployment.yaml \ + --set 'controller.enabled=true' \ + --set 'global.enableConsulNamespaces=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-enable-webhook-ca-update"))' | tee /dev/stderr) + [ "${actual}" = "false" ] +} + +@test "controller/Deployment: enable-webhook-ca-update flag is not set on command when using vault" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/controller-deployment.yaml \ + --set 'controller.enabled=true' \ + --set 'global.enableConsulNamespaces=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=test' \ + --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-enable-webhook-ca-update"))' | tee /dev/stderr) + [ "${actual}" = "true" ] +} + #-------------------------------------------------------------------- # replicas @@ -339,6 +392,17 @@ load _helpers [ "${actual}" = "key" ] } +@test "controller/Deployment: Adds -webhook-tls-cert-dir=/tmp/controller-webhook/certs to command" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/controller-deployment.yaml \ + --set 'controller.enabled=true' \ + --set 'global.tls.enabled=true' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-webhook-tls-cert-dir=/tmp/controller-webhook/certs"))' | tee /dev/stderr) + [ "${actual}" != "" ] +} + #-------------------------------------------------------------------- # global.tls.enableAutoEncrypt @@ -904,11 +968,21 @@ load _helpers --set 'global.secretsBackend.vault.enabled=true' \ --set 'global.secretsBackend.vault.consulClientRole=foo' \ --set 'global.secretsBackend.vault.consulServerRole=bar' \ - --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ --set 'global.tls.enabled=true' \ --set 'global.tls.enableAutoEncrypt=true' \ --set 'server.serverCert.secretName=pki_int/issue/test' \ --set 'global.tls.caCert.secretName=pki_int/cert/ca' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=pki/issue/controller-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ . | tee /dev/stderr | yq -r '.spec.template.metadata' | tee /dev/stderr) @@ -921,6 +995,19 @@ load _helpers yq -r '.annotations["vault.hashicorp.com/agent-inject-secret-serverca.crt"]' | tee /dev/stderr)" [ "${actual}" = "pki_int/cert/ca" ] + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-template-ca.crt"]' | tee /dev/stderr)" + local expected=$'{{- with secret \"foo/ca\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' + [ "${actual}" = "${expected}" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-secret-ca.crt"]' | tee /dev/stderr)" + [ "${actual}" = "foo/ca" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/secret-volume-path-ca.crt"]' | tee /dev/stderr)" + [ "${actual}" = "/vault/secrets/controller-webhook/certs" ] + local actual="$(echo $cmd | yq -r '.annotations["vault.hashicorp.com/agent-init-first"]' | tee /dev/stderr)" [ "${actual}" = "true" ] @@ -932,8 +1019,184 @@ load _helpers local actual="$(echo $cmd | yq -r '.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr)" [ "${actual}" = "test" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-secret-tls.crt"]' | tee /dev/stderr)" + [ "${actual}" = "pki/issue/controller-webhook-cert-dc1" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-template-tls.crt"]' | tee /dev/stderr)" + local expected=$'{{- with secret \"pki/issue/controller-webhook-cert-dc1\" \"common_name=release-name-consul-controller-webhook\"\n\"alt_names=release-name-consul-controller-webhook,release-name-consul-controller-webhook.default,release-name-consul-controller-webhook.default.svc,release-name-consul-controller-webhook.default.svc.cluster.local\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' + [ "${actual}" = "${expected}" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/secret-volume-path-tls.crt"]' | tee /dev/stderr)" + [ "${actual}" = "/vault/secrets/controller-webhook/certs" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-secret-tls.key"]' | tee /dev/stderr)" + [ "${actual}" = "pki/issue/controller-webhook-cert-dc1" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/agent-inject-template-tls.key"]' | tee /dev/stderr)" + local expected=$'{{- with secret \"pki/issue/controller-webhook-cert-dc1\" \"common_name=release-name-consul-controller-webhook\"\n\"alt_names=release-name-consul-controller-webhook,release-name-consul-controller-webhook.default,release-name-consul-controller-webhook.default.svc,release-name-consul-controller-webhook.default.svc.cluster.local\" -}}\n{{- .Data.private_key -}}\n{{- end -}}' + [ "${actual}" = "${expected}" ] + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/secret-volume-path-tls.key"]' | tee /dev/stderr)" + [ "${actual}" = "/vault/secrets/controller-webhook/certs" ] } +@test "controller/Deployment: vault does not add cert volume when global.tls.enabled is true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/controller-deployment.yaml \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'controller.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + . | tee /dev/stderr | + yq '.spec.template.spec.volumes[] | select(.name == "cert")' | tee /dev/stderr) + [ "${actual}" == "" ] +} + +@test "controller/Deployment: vault does not add cert volumeMounts when global.tls.enabled is true" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/controller-deployment.yaml \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'controller.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "cert")' | tee /dev/stderr) + [ "${actual}" == "" ] +} + +@test "controller/Deployment: vault webhook-tls-cert-dir flag is set to /vault/secrets" { + cd `chart_dir` + local actual=$(helm template \ + -s templates/controller-deployment.yaml \ + --set 'controller.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + . | tee /dev/stderr | + yq '.spec.template.spec.containers[0].command | any(contains("-webhook-tls-cert-dir=/vault/secrets"))' | tee /dev/stderr) + + [ "${actual}" = "true" ] +} + +@test "controller/Deployment: fails if vault is enabled and global.secretsBackend.vault.controllerRole is set but global.secretsBackend.vault.connectInject.tlsCert.secretName and global.secretsBackend.vault.connectInject.caCert.secretName are not" { + cd `chart_dir` + run helm template \ + -s templates/controller-deployment.yaml \ + --set 'controller.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.controllerRole=controllerinjectcarole' \ + --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' . + [ "$status" -eq 1 ] + [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName." ]] +} + +@test "controller/Deployment: fails if vault is enabled and global.secretsBackend.vault.controller.tlsCert.secretName is set but global.secretsBackend.vault.connectInjectRole and global.secretsBackend.vault.connectInject.caCert.secretName are not" { + cd `chart_dir` + run helm template \ + -s templates/controller-deployment.yaml \ + --set 'controller.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' . + [ "$status" -eq 1 ] + [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName." ]] +} + +@test "controller/Deployment: fails if vault is enabled and global.secretsBackend.vault.controller.caCert.secretName is set but global.secretsBackend.vault.connectInjectRole and global.secretsBackend.vault.connectInject.tlsCert.secretName are not" { + cd `chart_dir` + run helm template \ + -s templates/controller-deployment.yaml \ + --set 'controller.enabled=true' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.tls.caCert.secretName=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' . + [ "$status" -eq 1 ] + [[ "$output" =~ "When one of the following has been set, all must be set: global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and global.secretsBackend.vault.controller.caCert.secretName." ]] +} + +@test "controller/Deployment: vault vault.hashicorp.com/role set to global.secretsBackend.vault.controllerRole if global.secretsBackend.vault.controllerRole is not set" { + cd `chart_dir` + local cmd=$(helm template \ + -s templates/controller-deployment.yaml \ + --set 'controller.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + --set 'global.tls.enabled=true' \ + --set 'global.tls.enableAutoEncrypt=true' \ + --set 'server.serverCert.secretName=pki_int/issue/test' \ + --set 'global.tls.caCert.secretName=pki_int/cert/ca' \ + . | tee /dev/stderr | + yq -r '.spec.template.metadata' | tee /dev/stderr) + + local actual="$(echo $cmd | + yq -r '.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr)" + [ "${actual}" = "test2" ] +} #-------------------------------------------------------------------- # Vault agent annotations diff --git a/charts/consul/test/unit/webhook-cert-manager-clusterrole.bats b/charts/consul/test/unit/webhook-cert-manager-clusterrole.bats index 90b7a3e59e..5f7a03c319 100644 --- a/charts/consul/test/unit/webhook-cert-manager-clusterrole.bats +++ b/charts/consul/test/unit/webhook-cert-manager-clusterrole.bats @@ -40,6 +40,93 @@ load _helpers [ "${actual}" = "true" ] } +#-------------------------------------------------------------------- +# rules + +@test "webhookCertManager/ClusterRole: sets full access to secrets" { + cd `chart_dir` + local object=$(helm template \ + -s templates/webhook-cert-manager-clusterrole.yaml \ + --set 'controller.enabled=true' \ + . | tee /dev/stderr | + yq -r '.rules[0]' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.resources[0]' | tee /dev/stderr) + [ "${actual}" = "secrets" ] + + local actual=$(echo $object | yq -r '.apiGroups[0]' | tee /dev/stderr) + [ "${actual}" = "" ] + + local actual=$(echo $object | yq -r '.verbs | index("create")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("delete")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("get")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("list")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("patch")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("update")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("watch")' | tee /dev/stderr) + [ "${actual}" != null ] +} + +@test "webhookCertManager/ClusterRole: sets get, list, watch, and patch access to mutatingwebhookconfigurations" { + cd `chart_dir` + local object=$(helm template \ + -s templates/webhook-cert-manager-clusterrole.yaml \ + --set 'controller.enabled=true' \ + . | tee /dev/stderr | + yq -r '.rules[1]' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.resources[0]' | tee /dev/stderr) + [ "${actual}" = "mutatingwebhookconfigurations" ] + + local actual=$(echo $object | yq -r '.apiGroups[0]' | tee /dev/stderr) + [ "${actual}" = "admissionregistration.k8s.io" ] + + local actual=$(echo $object | yq -r '.verbs | index("get")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("list")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("patch")' | tee /dev/stderr) + [ "${actual}" != null ] + + local actual=$(echo $object | yq -r '.verbs | index("watch")' | tee /dev/stderr) + [ "${actual}" != null ] +} + +@test "webhookCertManager/ClusterRole: sets get access to deployments" { + cd `chart_dir` + local object=$(helm template \ + -s templates/webhook-cert-manager-clusterrole.yaml \ + --set 'controller.enabled=true' \ + . | tee /dev/stderr | + yq -r '.rules[2]' | tee /dev/stderr) + + local actual=$(echo $object | yq -r '.resources[0]' | tee /dev/stderr) + [ "${actual}" = "deployments" ] + + local actual=$(echo $object | yq -r '.apiGroups[0]' | tee /dev/stderr) + [ "${actual}" = "apps" ] + + local actual=$(echo $object | yq -r '.resourceNames[0]' | tee /dev/stderr) + [ "${actual}" = "release-name-consul-webhook-cert-manager" ] + + local actual=$(echo $object | yq -r '.verbs | index("get")' | tee /dev/stderr) + [ "${actual}" != null ] +} + #-------------------------------------------------------------------- # global.enablePodSecurityPolicies @@ -58,3 +145,27 @@ load _helpers local actual=$(echo $object | yq -r '.resourceNames[0]' | tee /dev/stderr) [ "${actual}" = "release-name-consul-webhook-cert-manager" ] } + +#-------------------------------------------------------------------- +# Vault + +@test "webhookCertManager/ClusterRole: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { + cd `chart_dir` + assert_empty helm template \ + -s templates/webhook-cert-manager-clusterrole.yaml \ + --set 'controller.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + . +} diff --git a/charts/consul/test/unit/webhook-cert-manager-clusterrolebinding.bats b/charts/consul/test/unit/webhook-cert-manager-clusterrolebinding.bats index 628b672062..ffabf41ee7 100644 --- a/charts/consul/test/unit/webhook-cert-manager-clusterrolebinding.bats +++ b/charts/consul/test/unit/webhook-cert-manager-clusterrolebinding.bats @@ -39,3 +39,27 @@ load _helpers yq 'length > 0' | tee /dev/stderr) [ "${actual}" = "true" ] } + +#-------------------------------------------------------------------- +# Vault + +@test "webhookCertManager/ClusterRoleBinding: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { + cd `chart_dir` + assert_empty helm template \ + -s templates/webhook-cert-manager-clusterrolebinding.yaml \ + --set 'controller.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + . +} diff --git a/charts/consul/test/unit/webhook-cert-manager-configmap.bats b/charts/consul/test/unit/webhook-cert-manager-configmap.bats index 31ec074f8a..7d7262b9af 100644 --- a/charts/consul/test/unit/webhook-cert-manager-configmap.bats +++ b/charts/consul/test/unit/webhook-cert-manager-configmap.bats @@ -90,4 +90,28 @@ load _helpers local actual=$(echo $cfg | jq '.[1].name | contains("controller")') [ "${actual}" = "true" ] -} \ No newline at end of file +} + +#-------------------------------------------------------------------- +# Vault + +@test "webhookCertManager/Configmap: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { + cd `chart_dir` + assert_empty helm template \ + -s templates/webhook-cert-manager-configmap.yaml \ + --set 'controller.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + . +} diff --git a/charts/consul/test/unit/webhook-cert-manager-deployment.bats b/charts/consul/test/unit/webhook-cert-manager-deployment.bats index f3118206bd..78b6e52997 100644 --- a/charts/consul/test/unit/webhook-cert-manager-deployment.bats +++ b/charts/consul/test/unit/webhook-cert-manager-deployment.bats @@ -62,3 +62,27 @@ load _helpers yq -r '.spec.template.spec.tolerations[0].key' | tee /dev/stderr) [ "${actual}" = "value" ] } + +#-------------------------------------------------------------------- +# Vault + +@test "webhookCertManager/Deployment: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { + cd `chart_dir` + assert_empty helm template \ + -s templates/webhook-cert-manager-deployment.yaml \ + --set 'controller.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + . +} diff --git a/charts/consul/test/unit/webhook-cert-manager-podsecuritypolicy.bats b/charts/consul/test/unit/webhook-cert-manager-podsecuritypolicy.bats index f4e8a2a22b..00bb55a2d4 100644 --- a/charts/consul/test/unit/webhook-cert-manager-podsecuritypolicy.bats +++ b/charts/consul/test/unit/webhook-cert-manager-podsecuritypolicy.bats @@ -28,7 +28,7 @@ load _helpers [ "${actual}" = "true" ] } -@test "webhookCertManager/Configmap: enabled with connectInject.enabled=true, controller.enabled=false and global.enablePodSecurityPolicies=true" { +@test "webhookCertManager/PodSecurityPolicy: enabled with connectInject.enabled=true, controller.enabled=false and global.enablePodSecurityPolicies=true" { cd `chart_dir` local actual=$(helm template \ -s templates/webhook-cert-manager-podsecuritypolicy.yaml \ @@ -39,7 +39,7 @@ load _helpers [ "${actual}" = "true" ] } -@test "webhookCertManager/Configmap: enabled with connectInject.enabled=true, controller.enabled=true and global.enablePodSecurityPolicies=true" { +@test "webhookCertManager/PodSecurityPolicy: enabled with connectInject.enabled=true, controller.enabled=true and global.enablePodSecurityPolicies=true" { cd `chart_dir` local actual=$(helm template \ -s templates/webhook-cert-manager-podsecuritypolicy.yaml \ @@ -50,3 +50,28 @@ load _helpers yq 'length > 0' | tee /dev/stderr) [ "${actual}" = "true" ] } + +#-------------------------------------------------------------------- +# Vault + +@test "webhookCertManager/PodSecurityPolicy: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { + cd `chart_dir` + assert_empty helm template \ + -s templates/webhook-cert-manager-podsecuritypolicy.yaml \ + --set 'global.enablePodSecurityPolicies=true' \ + --set 'controller.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + . +} diff --git a/charts/consul/test/unit/webhook-cert-manager-serviceaccount.bats b/charts/consul/test/unit/webhook-cert-manager-serviceaccount.bats index 3cdd1e3d0e..e4307c9409 100644 --- a/charts/consul/test/unit/webhook-cert-manager-serviceaccount.bats +++ b/charts/consul/test/unit/webhook-cert-manager-serviceaccount.bats @@ -60,3 +60,27 @@ load _helpers yq -r '.imagePullSecrets[1].name' | tee /dev/stderr) [ "${actual}" = "my-secret2" ] } + +#-------------------------------------------------------------------- +# Vault + +@test "webhookCertManager/ServiceAccount: disabled when the following are configured - global.secretsBackend.vault.enabled, global.secretsBackend.vault.connectInjectRole, global.secretsBackend.vault.connectInject.tlsCert.secretName, global.secretsBackend.vault.connectInject.caCert.secretName, global.secretsBackend.vault.controllerRole, global.secretsBackend.vault.controller.tlsCert.secretName, and .global.secretsBackend.vault.controller.caCert.secretName" { + cd `chart_dir` + assert_empty helm template \ + -s templates/webhook-cert-manager-serviceaccount.yaml \ + --set 'controller.enabled=true' \ + --set 'global.secretsBackend.vault.enabled=true' \ + --set 'global.secretsBackend.vault.consulClientRole=test' \ + --set 'global.secretsBackend.vault.consulServerRole=foo' \ + --set 'global.secretsBackend.vault.consulCARole=carole' \ + --set 'global.secretsBackend.vault.connectInjectRole=inject-ca-role' \ + --set 'global.secretsBackend.vault.connectInject.tlsCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.connectInject.caCert.secretName=pki/issue/connect-webhook-cert-dc1' \ + --set 'global.secretsBackend.vault.controllerRole=test' \ + --set 'global.secretsBackend.vault.controller.caCert.secretName=foo/ca' \ + --set 'global.secretsBackend.vault.controller.tlsCert.secretName=foo/tls' \ + --set 'global.secretsBackend.vault.consulClientRole=foo' \ + --set 'global.secretsBackend.vault.consulServerRole=bar' \ + --set 'global.secretsBackend.vault.consulCARole=test2' \ + . +} diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index 2d60b88d4a..87cb977846 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -195,6 +195,24 @@ global: # and check the name of `metadata.name`. adminPartitionsRole: "" + # The Vault role to read Consul controller's webhook's + # CA and issue a certificate and private key. + # A Vault policy must be created which grants issue capabilities to + # `global.secretsBackend.vault.controller.tlsCert.secretName`. + controllerRole: "" + + # The Vault role to read Consul connect-injector webhook's CA + # and issue a certificate and private key. + # A Vault policy must be created which grants issue capabilities to + # `global.secretsBackend.vault.connectInject.tlsCert.secretName`. + connectInjectRole: "" + + # The Vault role for all Consul components to read the Consul's server's CA Certificate (unauthenticated). + # The role should be connected to the service accounts of all Consul components, or alternatively `*` since it + # will be used only against the `pki/cert/ca` endpoint which is unauthenticated. A policy must be created which grants + # read capabilities to `global.tls.caCert.secretName`, which is usually `pki/cert/ca`. + consulCARole: "" + # This value defines additional annotations for # Vault agent on any pods where it'll be running. # This should be formatted as a multi-line string. @@ -208,12 +226,6 @@ global: # @type: string agentAnnotations: null - # The Vault role for all Consul components to read the Consul's server's CA Certificate (unauthenticated). - # The role should be connected to the service accounts of all Consul components, or alternatively `*` since it - # will be used only against the `pki/cert/ca` endpoint which is unauthenticated. A policy must be created which grants - # read capabilities to `global.tls.caCert.secretName`, which is usually `pki/cert/ca`. - consulCARole: "" - # Configuration for Vault server CA certificate. This certificate will be mounted # to any pod where Vault agent needs to run. ca: @@ -263,6 +275,44 @@ global: additionalConfig: | {} + controller: + # Configuration to the Vault Secret that Kubernetes will use on + # Kubernetes CRD creation, deletion, and update, to get TLS certificates + # used issued from vault to send webhooks to the controller. + tlsCert: + # The Vault secret path that issues TLS certificates for controller + # webhooks. + # @type: string + secretName: null + + # Configuration to the Vault Secret that Kubernetes will use on + # Kubernetes CRD creation, deletion, and update, to get CA certificates + # used issued from vault to send webhooks to the controller. + caCert: + # The Vault secret path that contains the CA certificate for controller + # webhooks. + # @type: string + secretName: null + + connectInject: + # Configuration to the Vault Secret that Kubernetes will use on + # Kubernetes pod creation, deletion, and update, to get CA certificates + # used issued from vault to send webhooks to the ConnectInject. + caCert: + # The Vault secret path that contains the CA certificate for + # Connect Inject webhooks. + # @type: string + secretName: null + + # Configuration to the Vault Secret that Kubernetes will use on + # Kubernetes pod creation, deletion, and update, to get TLS certificates + # used issued from vault to send webhooks to the ConnectInject. + tlsCert: + # The Vault secret path that issues TLS certificates for connect + # inject webhooks. + # @type: string + secretName: null + # Configures Consul's gossip encryption key. # (see `-encrypt` (https://www.consul.io/docs/agent/config/cli-flags#_encrypt)). # By default, gossip encryption is not enabled. The gossip encryption key may be set automatically or manually. diff --git a/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go new file mode 100644 index 0000000000..c3c93b5204 --- /dev/null +++ b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go @@ -0,0 +1,51 @@ +package mutatingwebhookconfiguration + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" +) + +// UpdateWithCABundle iterates over every webhook on the specified webhook configuration and updates +// their caBundle with the the specified CA. +func UpdateWithCABundle(ctx context.Context, clientset kubernetes.Interface, webhookConfigName string, caCert []byte) error { + if len(caCert) == 0 { + return errors.New("no CA certificate in the bundle") + } + value := base64.StdEncoding.EncodeToString(caCert) + webhookCfg, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, webhookConfigName, metav1.GetOptions{}) + + if err != nil { + return err + } + type patch struct { + Op string `json:"op,omitempty"` + Path string `json:"path,omitempty"` + Value string `json:"value,omitempty"` + } + + var patches []patch + for i := range webhookCfg.Webhooks { + patches = append(patches, patch{ + Op: "add", + Path: fmt.Sprintf("/webhooks/%d/clientConfig/caBundle", i), + Value: value, + }) + } + patchesJson, err := json.Marshal(patches) + if err != nil { + return err + } + + if _, err = clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Patch(ctx, webhookConfigName, types.JSONPatchType, patchesJson, metav1.PatchOptions{}); err != nil { + return err + } + + return nil +} diff --git a/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go new file mode 100644 index 0000000000..e247c71d14 --- /dev/null +++ b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go @@ -0,0 +1,44 @@ +package mutatingwebhookconfiguration + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + admissionv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +func TestUpdateWithCABundle_emptyCertReturnsError(t *testing.T) { + var bytes []byte + ctx := context.Background() + clientset := fake.NewSimpleClientset() + + err := UpdateWithCABundle(ctx, clientset, "foo", bytes) + require.Error(t, err, "no CA certificate in the bundle") +} + +func TestUpdateWithCABundle_patchesExistingConfiguration(t *testing.T) { + caBundleOne := []byte("ca-bundle-for-mwc") + ctx := context.Background() + clientset := fake.NewSimpleClientset() + + mwc := &admissionv1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mwc-one", + }, + Webhooks: []admissionv1.MutatingWebhook{ + { + Name: "webhook-under-test", + }, + }, + } + mwcCreated, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(ctx, mwc, metav1.CreateOptions{}) + require.NoError(t, err) + err = UpdateWithCABundle(ctx, clientset, mwcCreated.Name, caBundleOne) + require.NoError(t, err) + mwcFetched, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, mwc.Name, metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, caBundleOne, mwcFetched.Webhooks[0].ClientConfig.CABundle) +} diff --git a/control-plane/subcommand/controller/command.go b/control-plane/subcommand/controller/command.go index eb7121b928..f5815b0dfa 100644 --- a/control-plane/subcommand/controller/command.go +++ b/control-plane/subcommand/controller/command.go @@ -1,15 +1,18 @@ package controller import ( + "context" "errors" "flag" "fmt" + "io/ioutil" "sync" "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/controller" + mutatingwebhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/mutating-webhook-configuration" cmdCommon "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul/api" @@ -17,24 +20,30 @@ import ( "go.uber.org/zap/zapcore" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/kubernetes" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/webhook" ) +const WebhookCAFilename = "ca.crt" + type Command struct { UI cli.Ui flagSet *flag.FlagSet httpFlags *flags.HTTPFlags - flagWebhookTLSCertDir string - flagEnableLeaderElection bool - flagEnableWebhooks bool - flagDatacenter string - flagLogLevel string - flagLogJSON bool + flagWebhookTLSCertDir string + flagEnableLeaderElection bool + flagEnableWebhooks bool + flagDatacenter string + flagLogLevel string + flagLogJSON bool + flagResourcePrefix string + flagEnableWebhookCAUpdate bool // Flags to support Consul Enterprise namespaces. flagEnableNamespaces bool @@ -81,6 +90,10 @@ func (c *Command) init() { "Directory that contains the TLS cert and key required for the webhook. The cert and key files must be named 'tls.crt' and 'tls.key' respectively.") c.flagSet.BoolVar(&c.flagEnableWebhooks, "enable-webhooks", true, "Enable webhooks. Disable when running locally since Kube API server won't be able to route to local server.") + c.flagSet.StringVar(&c.flagResourcePrefix, "resource-prefix", "", + "Release prefix of the Consul installation used to prepend on the webhook name that will have its CA bundle updated.") + c.flagSet.BoolVar(&c.flagEnableWebhookCAUpdate, "enable-webhook-ca-update", false, + "Enables updating the CABundle on the webhook within this controller rather than using the webhook-cert-manager.") c.flagSet.StringVar(&c.flagLogLevel, "log-level", zapcore.InfoLevel.String(), fmt.Sprintf("Log verbosity level. Supported values (in order of detail) are "+ "%q, %q, %q, and %q.", zapcore.DebugLevel.String(), zapcore.InfoLevel.String(), zapcore.WarnLevel.String(), zapcore.ErrorLevel.String())) @@ -321,6 +334,14 @@ func (c *Command) Run(args []string) int { } // +kubebuilder:scaffold:builder + if c.flagEnableWebhookCAUpdate { + err := c.updateWebhookCABundle() + if err != nil { + setupLog.Error(err, "problem getting CA Cert") + return 1 + } + } + setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running manager") @@ -329,6 +350,32 @@ func (c *Command) Run(args []string) int { return 0 } +func (c *Command) updateWebhookCABundle() error { + // Create a context to be used by the processes started in this command. + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + config, err := rest.InClusterConfig() + if err != nil { + return err + } + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return err + } + + webhookConfigName := fmt.Sprintf("%s-controller", c.flagResourcePrefix) + caPath := fmt.Sprintf("%s/%s", c.flagWebhookTLSCertDir, WebhookCAFilename) + caCert, err := ioutil.ReadFile(caPath) + if err != nil { + return err + } + err = mutatingwebhookconfiguration.UpdateWithCABundle(ctx, clientset, webhookConfigName, caCert) + if err != nil { + return err + } + return nil +} + func (c *Command) validateFlags() error { if len(c.flagSet.Args()) > 0 { return errors.New("Invalid arguments: should have no non-flag arguments") diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 1554d0cb40..ef15998244 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -13,6 +13,7 @@ import ( connectinject "github.com/hashicorp/consul-k8s/control-plane/connect-inject" "github.com/hashicorp/consul-k8s/control-plane/consul" + mutatingwebhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/mutating-webhook-configuration" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul/api" @@ -31,22 +32,25 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" ) +const WebhookCAFilename = "ca.crt" + type Command struct { UI cli.Ui - flagListen string - flagCertDir string // Directory with TLS certs for listening (PEM) - flagDefaultInject bool // True to inject by default - flagConsulImage string // Docker image for Consul - flagEnvoyImage string // Docker image for Envoy - flagConsulK8sImage string // Docker image for consul-k8s - flagACLAuthMethod string // Auth Method to use for ACLs, if enabled - flagWriteServiceDefaults bool // True to enable central config injection - flagDefaultProtocol string // Default protocol for use with central config - flagConsulCACert string // [Deprecated] Path to CA Certificate to use when communicating with Consul clients - flagEnvoyExtraArgs string // Extra envoy args when starting envoy - flagLogLevel string - flagLogJSON bool + flagListen string + flagCertDir string // Directory with TLS certs for listening (PEM) + flagDefaultInject bool // True to inject by default + flagConsulImage string // Docker image for Consul + flagEnvoyImage string // Docker image for Envoy + flagConsulK8sImage string // Docker image for consul-k8s + flagACLAuthMethod string // Auth Method to use for ACLs, if enabled + flagWriteServiceDefaults bool // True to enable central config injection + flagDefaultProtocol string // Default protocol for use with central config + flagConsulCACert string // [Deprecated] Path to CA Certificate to use when communicating with Consul clients + flagEnvoyExtraArgs string // Extra envoy args when starting envoy + flagEnableWebhookCAUpdate bool + flagLogLevel string + flagLogJSON bool flagAllowK8sNamespacesList []string // K8s namespaces to explicitly inject flagDenyK8sNamespacesList []string // K8s namespaces to deny injection (has precedence) @@ -172,6 +176,8 @@ func (c *Command) init() { "Release prefix of the Consul installation used to determine Consul DNS Service name.") c.flagSet.BoolVar(&c.flagEnableOpenShift, "enable-openshift", false, "Indicates that the command runs in an OpenShift cluster.") + c.flagSet.BoolVar(&c.flagEnableWebhookCAUpdate, "enable-webhook-ca-update", false, + "Enables updating the CABundle on the webhook within this controller rather than using the web cert manager.") c.flagSet.StringVar(&c.flagLogLevel, "log-level", zapcore.InfoLevel.String(), fmt.Sprintf("Log verbosity level. Supported values (in order of detail) are "+ "%q, %q, %q, and %q.", zapcore.DebugLevel.String(), zapcore.InfoLevel.String(), zapcore.WarnLevel.String(), zapcore.ErrorLevel.String())) @@ -464,6 +470,14 @@ func (c *Command) Run(args []string) int { ConsulAPITimeout: c.http.ConsulAPITimeout(), }}) + if c.flagEnableWebhookCAUpdate { + err := c.updateWebhookCABundle(ctx) + if err != nil { + setupLog.Error(err, "problem getting CA Cert") + return 1 + } + } + if err := mgr.Start(ctx); err != nil { setupLog.Error(err, "problem running manager") return 1 @@ -472,6 +486,19 @@ func (c *Command) Run(args []string) int { return 0 } +func (c *Command) updateWebhookCABundle(ctx context.Context) error { + webhookConfigName := fmt.Sprintf("%s-connect-injector", c.flagResourcePrefix) + caPath := fmt.Sprintf("%s/%s", c.flagCertDir, WebhookCAFilename) + caCert, err := ioutil.ReadFile(caPath) + if err != nil { + return err + } + err = mutatingwebhookconfiguration.UpdateWithCABundle(ctx, c.clientset, webhookConfigName, caCert) + if err != nil { + return err + } + return nil +} func (c *Command) validateFlags() error { if c.flagConsulK8sImage == "" { return errors.New("-consul-k8s-image must be set") diff --git a/control-plane/subcommand/webhook-cert-manager/command.go b/control-plane/subcommand/webhook-cert-manager/command.go index 570a432ad9..0c35073dfd 100644 --- a/control-plane/subcommand/webhook-cert-manager/command.go +++ b/control-plane/subcommand/webhook-cert-manager/command.go @@ -3,7 +3,6 @@ package webhookcertmanager import ( "bytes" "context" - "encoding/base64" "encoding/json" "errors" "flag" @@ -17,6 +16,7 @@ import ( "time" "github.com/hashicorp/consul-k8s/control-plane/helper/cert" + mutatingwebhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/mutating-webhook-configuration" "github.com/hashicorp/consul-k8s/control-plane/subcommand" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" @@ -26,7 +26,6 @@ import ( corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" ) @@ -265,7 +264,8 @@ func (c *Command) reconcileCertificates(ctx context.Context, clientset kubernete } iterLog.Info("Updating webhook configuration") - if err = c.updateWebhookConfig(ctx, bundle, clientset); err != nil { + err = mutatingwebhookconfiguration.UpdateWithCABundle(ctx, c.clientset, bundle.WebhookConfigName, bundle.CACert) + if err != nil { iterLog.Error("Error updating webhook configuration") return err } @@ -307,39 +307,9 @@ func (c *Command) reconcileCertificates(ctx context.Context, clientset kubernete } iterLog.Info("Updating webhook configuration with new CA") - if err = c.updateWebhookConfig(ctx, bundle, clientset); err != nil { - iterLog.Error("Error updating webhook configuration", "err", err) - return err - } - return nil -} - -// updateWebhookConfig iterates over every webhook on the specified webhook configuration and updates -// their caBundle with the CA from the MetaBundle. -func (c *Command) updateWebhookConfig(ctx context.Context, metaBundle cert.MetaBundle, clientset kubernetes.Interface) error { - if len(metaBundle.CACert) == 0 { - return errors.New("no CA certificate in the bundle") - } - value := base64.StdEncoding.EncodeToString(metaBundle.CACert) - - webhookCfg, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, metaBundle.WebhookConfigName, metav1.GetOptions{}) + err = mutatingwebhookconfiguration.UpdateWithCABundle(ctx, clientset, bundle.WebhookConfigName, bundle.CACert) if err != nil { - return err - } - var patches []patch - for i := range webhookCfg.Webhooks { - patches = append(patches, patch{ - Op: "add", - Path: fmt.Sprintf("/webhooks/%d/clientConfig/caBundle", i), - Value: value, - }) - } - patchesJson, err := json.Marshal(patches) - if err != nil { - return err - } - - if _, err = clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Patch(ctx, metaBundle.WebhookConfigName, types.JSONPatchType, patchesJson, metav1.PatchOptions{}); err != nil { + iterLog.Error("Error updating webhook configuration", "err", err) return err } return nil @@ -396,12 +366,6 @@ func (c webhookConfig) validate(ctx context.Context, client kubernetes.Interface return nil } -type patch struct { - Op string `json:"op,omitempty"` - Path string `json:"path,omitempty"` - Value string `json:"value,omitempty"` -} - func (c *Command) Help() string { c.once.Do(c.init) return c.help