From 7a9a1f93eab1d91676415a5dff161ee63bfa2598 Mon Sep 17 00:00:00 2001 From: Bence Csati Date: Tue, 26 Mar 2024 20:47:49 +0100 Subject: [PATCH] refactor: finished Signed-off-by: Bence Csati --- .github/workflows/ci.yaml | 11 +- docker-compose.yaml | 18 +- .../bao/client_logger.go} | 30 +- pkg/provider/bao/config.go | 23 +- pkg/provider/vault/client_logger.go | 59 + pkg/provider/vault/config.go | 22 +- pkg/webhook/pod.go | 1734 +++++++++++------ pkg/webhook/pod_test.go | 16 +- pkg/webhook/webhook.go | 8 +- 9 files changed, 1350 insertions(+), 571 deletions(-) rename pkg/{webhook/vault_client_logger.go => provider/bao/client_logger.go} (54%) create mode 100644 pkg/provider/vault/client_logger.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bb1539e..eec07c0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -61,6 +61,15 @@ jobs: VAULT_DEV_ROOT_TOKEN_ID: 227e1cce-6bf7-30bb-2d2a-acc854318caf ports: - 8200:8200 + bao: + image: csatib02/openbao:dev + env: + SKIP_SETCAP: "true" + BAO_ADDR: http://127.0.0.1:8200 + BAO_TOKEN: 227e1cce-6bf7-30bb-2d2a-acc854318caf + BAO_DEV_ROOT_TOKEN_ID: 227e1cce-6bf7-30bb-2d2a-acc854318caf + ports: + - 8300:8200 steps: - name: Checkout repository @@ -260,7 +269,7 @@ jobs: # run: nix develop --impure .#ci -c make test-e2e run: nix develop --impure .#ci -c make test-e2e-local env: - KIND_K8S_VERSION: ${{ matrix.k8s_version } + KIND_K8S_VERSION: ${{ matrix.k8s_version }} LOAD_IMAGE_ARCHIVE: ${{ github.workspace }}/docker.tar # VAULT_VERSION: ${{ matrix.vault_version }} WEBHOOK_VERSION: ${{ needs.artifacts.outputs.container-image-tag }} diff --git a/docker-compose.yaml b/docker-compose.yaml index ac32c58..f1ce608 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -2,9 +2,23 @@ version: "3.9" services: vault: - image: hashicorp/vault:1.14.8 + container_name: secrets-webhook-vault + image: hashicorp/vault:1.14.1 ports: - 127.0.0.1:8200:8200 environment: - SKIP_SETCAP: true + SKIP_SETCAP: "true" + VAULT_ADDR: http://127.0.0.1:8200 + VAULT_TOKEN: 227e1cce-6bf7-30bb-2d2a-acc854318caf VAULT_DEV_ROOT_TOKEN_ID: 227e1cce-6bf7-30bb-2d2a-acc854318caf + + bao: + container_name: secrets-webhook-bao + image: csatib02/openbao:dev + ports: + - 127.0.0.1:8300:8200 + environment: + SKIP_SETCAP: "true" + BAO_ADDR: http://127.0.0.1:8200 + BAO_TOKEN: 227e1cce-6bf7-30bb-2d2a-acc854318caf + BAO_DEV_ROOT_TOKEN_ID: 227e1cce-6bf7-30bb-2d2a-acc854318caf \ No newline at end of file diff --git a/pkg/webhook/vault_client_logger.go b/pkg/provider/bao/client_logger.go similarity index 54% rename from pkg/webhook/vault_client_logger.go rename to pkg/provider/bao/client_logger.go index 73d6765..8fad68d 100644 --- a/pkg/webhook/vault_client_logger.go +++ b/pkg/provider/bao/client_logger.go @@ -12,41 +12,41 @@ // See the License for the specific language governing permissions and // limitations under the License. -package webhook +package bao import ( "log/slog" - "github.com/bank-vaults/vault-sdk/vault" + baosdk "github.com/bank-vaults/vault-sdk/vault" ) -var _ vault.Logger = &clientLogger{} +var _ baosdk.Logger = &ClientLogger{} -type clientLogger struct { - logger *slog.Logger +type ClientLogger struct { + Logger *slog.Logger } -func (l clientLogger) Trace(msg string, args ...map[string]interface{}) { +func (l ClientLogger) Trace(msg string, args ...map[string]interface{}) { l.Debug(msg, args...) } -func (l clientLogger) Debug(msg string, args ...map[string]interface{}) { - l.logger.Debug(msg, l.argsToAttrs(args...)...) +func (l ClientLogger) Debug(msg string, args ...map[string]interface{}) { + l.Logger.Debug(msg, l.argsToAttrs(args...)...) } -func (l clientLogger) Info(msg string, args ...map[string]interface{}) { - l.logger.Info(msg, l.argsToAttrs(args...)...) +func (l ClientLogger) Info(msg string, args ...map[string]interface{}) { + l.Logger.Info(msg, l.argsToAttrs(args...)...) } -func (l clientLogger) Warn(msg string, args ...map[string]interface{}) { - l.logger.Warn(msg, l.argsToAttrs(args...)...) +func (l ClientLogger) Warn(msg string, args ...map[string]interface{}) { + l.Logger.Warn(msg, l.argsToAttrs(args...)...) } -func (l clientLogger) Error(msg string, args ...map[string]interface{}) { - l.logger.Error(msg, l.argsToAttrs(args...)...) +func (l ClientLogger) Error(msg string, args ...map[string]interface{}) { + l.Logger.Error(msg, l.argsToAttrs(args...)...) } -func (clientLogger) argsToAttrs(args ...map[string]interface{}) []any { +func (ClientLogger) argsToAttrs(args ...map[string]interface{}) []any { var attrs []any for _, arg := range args { diff --git a/pkg/provider/bao/config.go b/pkg/provider/bao/config.go index edd5ab9..bd700f2 100644 --- a/pkg/provider/bao/config.go +++ b/pkg/provider/bao/config.go @@ -19,7 +19,6 @@ import ( "html/template" "log/slog" "strconv" - "strings" "time" @@ -33,7 +32,27 @@ import ( "github.com/bank-vaults/secrets-webhook/pkg/common" ) -const ProviderName = "bao" +const ( + AgentConfig = ` +pid_file = "/tmp/pidfile" + +auto_auth { + method "kubernetes" { + namespace = "%s" + mount_path = "auth/%s" + config = { + role = "%s" + } + } + + sink "file" { + config = { + path = "/bao/.bao-token" + } + } +}` + ProviderName = "bao" +) type Config struct { ObjectNamespace string diff --git a/pkg/provider/vault/client_logger.go b/pkg/provider/vault/client_logger.go new file mode 100644 index 0000000..40b049d --- /dev/null +++ b/pkg/provider/vault/client_logger.go @@ -0,0 +1,59 @@ +// Copyright © 2023 Bank-Vaults Maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vault + +import ( + "log/slog" + + vaultsdk "github.com/bank-vaults/vault-sdk/vault" +) + +var _ vaultsdk.Logger = &ClientLogger{} + +type ClientLogger struct { + Logger *slog.Logger +} + +func (l ClientLogger) Trace(msg string, args ...map[string]interface{}) { + l.Debug(msg, args...) +} + +func (l ClientLogger) Debug(msg string, args ...map[string]interface{}) { + l.Logger.Debug(msg, l.argsToAttrs(args...)...) +} + +func (l ClientLogger) Info(msg string, args ...map[string]interface{}) { + l.Logger.Info(msg, l.argsToAttrs(args...)...) +} + +func (l ClientLogger) Warn(msg string, args ...map[string]interface{}) { + l.Logger.Warn(msg, l.argsToAttrs(args...)...) +} + +func (l ClientLogger) Error(msg string, args ...map[string]interface{}) { + l.Logger.Error(msg, l.argsToAttrs(args...)...) +} + +func (ClientLogger) argsToAttrs(args ...map[string]interface{}) []any { + var attrs []any + + for _, arg := range args { + for key, value := range arg { + attrs = append(attrs, slog.Any(key, value)) + } + } + + return attrs +} diff --git a/pkg/provider/vault/config.go b/pkg/provider/vault/config.go index acd9153..9e1de40 100644 --- a/pkg/provider/vault/config.go +++ b/pkg/provider/vault/config.go @@ -32,7 +32,27 @@ import ( "github.com/bank-vaults/secrets-webhook/pkg/common" ) -const ProviderName = "vault" +const ( + AgentConfig = ` +pid_file = "/tmp/pidfile" + +auto_auth { + method "kubernetes" { + namespace = "%s" + mount_path = "auth/%s" + config = { + role = "%s" + } + } + + sink "file" { + config = { + path = "/vault/.vault-token" + } + } +}` + ProviderName = "vault" +) type Config struct { ObjectNamespace string diff --git a/pkg/webhook/pod.go b/pkg/webhook/pod.go index 5ad5ebf..ab32557 100644 --- a/pkg/webhook/pod.go +++ b/pkg/webhook/pod.go @@ -33,27 +33,7 @@ import ( "github.com/bank-vaults/secrets-webhook/pkg/provider/vault" ) -const ( - vaultAgentConfig = ` -pid_file = "/tmp/pidfile" - -auto_auth { - method "kubernetes" { - namespace = "%s" - mount_path = "auth/%s" - config = { - role = "%s" - } - } - - sink "file" { - config = { - path = "/vault/.vault-token" - } - } -}` - SecretInitVolumeName = "secret-init" -) +const SecretInitVolumeName = "secret-init" func (mw *MutatingWebhook) MutatePod(ctx context.Context, pod *corev1.Pod, webhookConfig common.Config, secretInitConfig common.SecretInitConfig, dryRun bool, configs []interface{}) error { mw.logger.Debug("Successfully connected to the API") @@ -67,13 +47,14 @@ func (mw *MutatingWebhook) MutatePod(ctx context.Context, pod *corev1.Pod, webho if err != nil { return errors.Wrap(err, "failed to mutate secret") } + case bao.Config: currentlyUsedProvider = bao.ProviderName - // err := mw.mutatePodForBao(ctx, pod, webhookConfig, secretInitConfig, providerConfig, dryRun) - // if err != nil { - // return errors.Wrap(err, "failed to mutate secret") - // } + err := mw.mutatePodForBao(ctx, pod, webhookConfig, secretInitConfig, providerConfig, dryRun) + if err != nil { + return errors.Wrap(err, "failed to mutate secret") + } default: return errors.Errorf("unknown provider config type: %T", config) @@ -83,66 +64,23 @@ func (mw *MutatingWebhook) MutatePod(ctx context.Context, pod *corev1.Pod, webho return nil } -func getBaseSecurityContext(podSecurityContext *corev1.PodSecurityContext, webhookConfig common.Config) *corev1.SecurityContext { - context := &corev1.SecurityContext{ - AllowPrivilegeEscalation: &webhookConfig.PspAllowPrivilegeEscalation, - ReadOnlyRootFilesystem: &webhookConfig.ReadOnlyRootFilesystem, - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{}, - Drop: []corev1.Capability{ - "ALL", - }, - }, - } - - if podSecurityContext != nil && podSecurityContext.RunAsUser != nil { - context.RunAsUser = podSecurityContext.RunAsUser - } - - // Although it could explicitly be set to false, - // the behavior of false and unset are the same - if webhookConfig.RunAsNonRoot { - context.RunAsNonRoot = &webhookConfig.RunAsNonRoot - } - - if webhookConfig.RunAsUser > 0 { - context.RunAsUser = &webhookConfig.RunAsUser - } - - if webhookConfig.RunAsGroup > 0 { - context.RunAsGroup = &webhookConfig.RunAsGroup - } - - return context -} - -// isLogLevelSet checks if the SECRET_INIT_LOG_LEVEL environment variable -// has already been set in the container, so it doesn't get overridden. -func isLogLevelSet(envVars []corev1.EnvVar) bool { - for _, envVar := range envVars { - if envVar.Name == "SECRET_INIT_LOG_LEVEL" { - return true - } - } - return false -} - func isPodAlreadyMutated(pod *corev1.Pod) bool { for _, volume := range pod.Spec.Volumes { if volume.Name == SecretInitVolumeName { return true } } + return false } -func (mw *MutatingWebhook) mutateContainers(ctx context.Context, containers []corev1.Container, podSpec *corev1.PodSpec, webhookConfig common.Config, secretInitConfig common.SecretInitConfig, vaultConfig vault.Config) (bool, error) { +func (mw *MutatingWebhook) mutateContainers(ctx context.Context, containers []corev1.Container, podSpec *corev1.PodSpec, webhookConfig common.Config, secretInitConfig common.SecretInitConfig, config interface{}, objectNamespace string, fromPath string) (bool, error) { mutated := false for i, container := range containers { var envVars []corev1.EnvVar if len(container.EnvFrom) > 0 { - envFrom, err := mw.lookForEnvFrom(container.EnvFrom, vaultConfig.ObjectNamespace) + envFrom, err := mw.lookForEnvFrom(container.EnvFrom, objectNamespace) if err != nil { return false, err } @@ -153,11 +91,13 @@ func (mw *MutatingWebhook) mutateContainers(ctx context.Context, containers []co if hasProviderPrefix(currentlyUsedProvider, env.Value, true) { envVars = append(envVars, env) } + if env.ValueFrom != nil { - valueFrom, err := mw.lookForValueFrom(env, vaultConfig.ObjectNamespace) + valueFrom, err := mw.lookForValueFrom(env, objectNamespace) if err != nil { return false, err } + if valueFrom == nil { continue } @@ -165,7 +105,7 @@ func (mw *MutatingWebhook) mutateContainers(ctx context.Context, containers []co } } - if len(envVars) == 0 && vaultConfig.FromPath == "" { + if len(envVars) == 0 && fromPath == "" { continue } @@ -175,7 +115,7 @@ func (mw *MutatingWebhook) mutateContainers(ctx context.Context, containers []co // the container has no explicitly specified command if len(args) == 0 { - imageConfig, err := mw.registry.GetImageConfig(ctx, mw.k8sClient, vaultConfig.ObjectNamespace, webhookConfig.RegistrySkipVerify, &container, podSpec) //nolint:gosec + imageConfig, err := mw.registry.GetImageConfig(ctx, mw.k8sClient, objectNamespace, webhookConfig.RegistrySkipVerify, &container, podSpec) //nolint:gosec if err != nil { return false, err } @@ -202,6 +142,7 @@ func (mw *MutatingWebhook) mutateContainers(ctx context.Context, containers []co container.LivenessProbe.Exec.Command = []string{"/bank-vaults/secret-init"} container.LivenessProbe.Exec.Command = append(container.LivenessProbe.Exec.Command, lProbeCmd...) } + // mutate LivenessProbe if container.ReadinessProbe != nil && container.ReadinessProbe.Exec != nil { rProbeCmd := container.ReadinessProbe.Exec.Command @@ -247,142 +188,283 @@ func (mw *MutatingWebhook) mutateContainers(ctx context.Context, containers []co }...) } - switch currentlyUsedProvider { - case vault.ProviderName: + mw.setEnvVarsForProvider(&container, podSpec, secretInitConfig, config) + + containers[i] = container + } + + return mutated, nil +} + +func (mw *MutatingWebhook) setEnvVarsForProvider(container *corev1.Container, podSpec *corev1.PodSpec, secretInitConfig common.SecretInitConfig, config interface{}) { + switch config := config.(type) { + case vault.Config: + container.Env = append(container.Env, []corev1.EnvVar{ + { + Name: "VAULT_ADDR", + Value: config.Addr, + }, + { + Name: "VAULT_SKIP_VERIFY", + Value: strconv.FormatBool(config.SkipVerify), + }, + { + Name: "VAULT_AUTH_METHOD", + Value: config.AuthMethod, + }, + { + Name: "VAULT_PATH", + Value: config.Path, + }, + { + Name: "VAULT_ROLE", + Value: config.Role, + }, + { + Name: "VAULT_IGNORE_MISSING_SECRETS", + Value: config.IgnoreMissingSecrets, + }, + { + Name: "VAULT_PASSTHROUGH", + Value: config.Passthrough, + }, + { + Name: "SECRET_INIT_JSON_LOG", + Value: secretInitConfig.JSONLog, + }, + { + Name: "VAULT_CLIENT_TIMEOUT", + Value: config.ClientTimeout.String(), + }, + }...) + + if config.Token != "" { + container.Env = append(container.Env, corev1.EnvVar{ + Name: "VAULT_TOKEN", + Value: config.Token, + }) + } + + if len(config.TransitKeyID) > 0 { container.Env = append(container.Env, []corev1.EnvVar{ { - Name: "VAULT_ADDR", - Value: vaultConfig.Addr, - }, - { - Name: "VAULT_SKIP_VERIFY", - Value: strconv.FormatBool(vaultConfig.SkipVerify), + Name: "VAULT_TRANSIT_KEY_ID", + Value: config.TransitKeyID, }, + }...) + } + + if len(config.TransitPath) > 0 { + container.Env = append(container.Env, []corev1.EnvVar{ { - Name: "VAULT_AUTH_METHOD", - Value: vaultConfig.AuthMethod, + Name: "VAULT_TRANSIT_PATH", + Value: config.TransitPath, }, + }...) + } + + if config.TransitBatchSize > 0 { + container.Env = append(container.Env, []corev1.EnvVar{ { - Name: "VAULT_PATH", - Value: vaultConfig.Path, + Name: "VAULT_TRANSIT_BATCH_SIZE", + Value: strconv.Itoa(config.TransitBatchSize), }, + }...) + } + + if len(config.VaultNamespace) > 0 { + container.Env = append(container.Env, []corev1.EnvVar{ { - Name: "VAULT_ROLE", - Value: vaultConfig.Role, + Name: "VAULT_NAMESPACE", + Value: config.VaultNamespace, }, + }...) + } + + if config.TLSSecret != "" { + mountPath := "/vault/tls/" + volumeName := "vault-tls" + if hasTLSVolume(podSpec.Volumes, volumeName) { + mountPath = "/secret-init/tls/" + volumeName = "secret-init-tls" + } + + container.Env = append(container.Env, corev1.EnvVar{ + Name: "VAULT_CACERT", + Value: mountPath + "ca.crt", + }) + container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ + Name: volumeName, + MountPath: mountPath, + }) + } + + if config.UseAgent || config.TokenAuthMount != "" { + container.Env = append(container.Env, corev1.EnvVar{ + Name: "VAULT_TOKEN_FILE", + Value: "/vault/.vault-token", + }) + } + + if config.FromPath != "" { + container.Env = append(container.Env, corev1.EnvVar{ + Name: "VAULT_FROM_PATH", + Value: config.FromPath, + }) + } + + case bao.Config: + container.Env = append(container.Env, []corev1.EnvVar{ + { + Name: "BAO_ADDR", + Value: config.Addr, + }, + { + Name: "BAO_SKIP_VERIFY", + Value: strconv.FormatBool(config.SkipVerify), + }, + { + Name: "BAO_AUTH_METHOD", + Value: config.AuthMethod, + }, + { + Name: "BAO_PATH", + Value: config.Path, + }, + { + Name: "BAO_ROLE", + Value: config.Role, + }, + { + Name: "BAO_IGNORE_MISSING_SECRETS", + Value: config.IgnoreMissingSecrets, + }, + { + Name: "BAO_PASSTHROUGH", + Value: config.Passthrough, + }, + { + Name: "SECRET_INIT_JSON_LOG", + Value: secretInitConfig.JSONLog, + }, + { + Name: "BAO_CLIENT_TIMEOUT", + Value: config.ClientTimeout.String(), + }, + }...) + + if config.Token != "" { + container.Env = append(container.Env, corev1.EnvVar{ + Name: "BAO_TOKEN", + Value: config.Token, + }) + } + + if len(config.TransitKeyID) > 0 { + container.Env = append(container.Env, []corev1.EnvVar{ { - Name: "VAULT_IGNORE_MISSING_SECRETS", - Value: vaultConfig.IgnoreMissingSecrets, + Name: "BAO_TRANSIT_KEY_ID", + Value: config.TransitKeyID, }, + }...) + } + + if len(config.TransitPath) > 0 { + container.Env = append(container.Env, []corev1.EnvVar{ { - Name: "VAULT_PASSTHROUGH", - Value: vaultConfig.Passthrough, + Name: "BAO_TRANSIT_PATH", + Value: config.TransitPath, }, + }...) + } + + if config.TransitBatchSize > 0 { + container.Env = append(container.Env, []corev1.EnvVar{ { - Name: "SECRET_INIT_JSON_LOG", - Value: secretInitConfig.JSONLog, + Name: "BAO_TRANSIT_BATCH_SIZE", + Value: strconv.Itoa(config.TransitBatchSize), }, + }...) + } + + if len(config.BaoNamespace) > 0 { + container.Env = append(container.Env, []corev1.EnvVar{ { - Name: "VAULT_CLIENT_TIMEOUT", - Value: vaultConfig.ClientTimeout.String(), + Name: "BAO_NAMESPACE", + Value: config.BaoNamespace, }, }...) + } - if vaultConfig.Token != "" { - container.Env = append(container.Env, corev1.EnvVar{ - Name: "VAULT_TOKEN", - Value: vaultConfig.Token, - }) - } - - if len(vaultConfig.TransitKeyID) > 0 { - container.Env = append(container.Env, []corev1.EnvVar{ - { - Name: "VAULT_TRANSIT_KEY_ID", - Value: vaultConfig.TransitKeyID, - }, - }...) - } - - if len(vaultConfig.TransitPath) > 0 { - container.Env = append(container.Env, []corev1.EnvVar{ - { - Name: "VAULT_TRANSIT_PATH", - Value: vaultConfig.TransitPath, - }, - }...) - } - - if vaultConfig.TransitBatchSize > 0 { - container.Env = append(container.Env, []corev1.EnvVar{ - { - Name: "VAULT_TRANSIT_BATCH_SIZE", - Value: strconv.Itoa(vaultConfig.TransitBatchSize), - }, - }...) + if config.TLSSecret != "" { + mountPath := "/bao/tls/" + volumeName := "bao-tls" + if hasTLSVolume(podSpec.Volumes, volumeName) { + mountPath = "/secret-init/tls/" + volumeName = "secret-init-tls" } - if len(vaultConfig.VaultNamespace) > 0 { - container.Env = append(container.Env, []corev1.EnvVar{ - { - Name: "VAULT_NAMESPACE", - Value: vaultConfig.VaultNamespace, - }, - }...) - } + container.Env = append(container.Env, corev1.EnvVar{ + Name: "BAO_CACERT", + Value: mountPath + "ca.crt", + }) + container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ + Name: volumeName, + MountPath: mountPath, + }) + } - if vaultConfig.TLSSecret != "" { - mountPath := "/vault/tls/" - volumeName := "vault-tls" - if hasTLSVolume_Vault(podSpec.Volumes) { - mountPath = "/secret-init/tls/" - volumeName = "secret-init-tls" - } + if config.UseAgent || config.TokenAuthMount != "" { + container.Env = append(container.Env, corev1.EnvVar{ + Name: "BAO_TOKEN_FILE", + Value: "/bao/.bao-token", + }) + } - container.Env = append(container.Env, corev1.EnvVar{ - Name: "VAULT_CACERT", - Value: mountPath + "ca.crt", - }) - container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ - Name: volumeName, - MountPath: mountPath, - }) - } + if config.FromPath != "" { + container.Env = append(container.Env, corev1.EnvVar{ + Name: "BAO_FROM_PATH", + Value: config.FromPath, + }) + } - if vaultConfig.UseAgent || vaultConfig.TokenAuthMount != "" { - container.Env = append(container.Env, corev1.EnvVar{ - Name: "VAULT_TOKEN_FILE", - Value: "/vault/.vault-token", - }) - } + default: + mw.logger.Error("Unknown provider config type") + } +} - if vaultConfig.FromPath != "" { - container.Env = append(container.Env, corev1.EnvVar{ - Name: "VAULT_FROM_PATH", - Value: vaultConfig.FromPath, - }) - } +// isLogLevelSet checks if the SECRET_INIT_LOG_LEVEL environment variable +// has already been set in the container, so it doesn't get overridden. +func isLogLevelSet(envVars []corev1.EnvVar) bool { + for _, envVar := range envVars { + if envVar.Name == "SECRET_INIT_LOG_LEVEL" { + return true + } + } - case bao.ProviderName: + return false +} - default: - return false, errors.Errorf("unknown provider: %s", currentlyUsedProvider) +// If the original Pod contained a Volume "{providerName}-tls", for example Vault instances provisioned by the Operator +// we need to handle that edge case and choose another name for the vault-tls volume for accessing Vault with TLS. +func hasTLSVolume(volumes []corev1.Volume, tls string) bool { + for _, volume := range volumes { + if volume.Name == tls { + return true } - - containers[i] = container } - return mutated, nil + return false } -func (mw *MutatingWebhook) addSecretsVolToContainers_Vault(vaultConfig vault.Config, containers []corev1.Container) { +func (mw *MutatingWebhook) addSecretsVolToContainers(containers []corev1.Container, configFilePath string) { for i, container := range containers { mw.logger.Debug(fmt.Sprintf("Add secrets VolumeMount to container %s", container.Name)) container.VolumeMounts = append(container.VolumeMounts, []corev1.VolumeMount{ { Name: "ct-secrets", - MountPath: vaultConfig.ConfigfilePath, + MountPath: configFilePath, }, }...) @@ -390,14 +472,14 @@ func (mw *MutatingWebhook) addSecretsVolToContainers_Vault(vaultConfig vault.Con } } -func (mw *MutatingWebhook) addAgentSecretsVolToContainers_Vault(vaultConfig vault.Config, containers []corev1.Container) { +func (mw *MutatingWebhook) addAgentSecretsVolToContainers(containers []corev1.Container, configFilePath string) { for i, container := range containers { mw.logger.Debug(fmt.Sprintf("Add secrets VolumeMount to container %s", container.Name)) container.VolumeMounts = append(container.VolumeMounts, []corev1.VolumeMount{ { Name: "agent-secrets", - MountPath: vaultConfig.ConfigfilePath, + MountPath: configFilePath, }, }...) @@ -405,44 +487,440 @@ func (mw *MutatingWebhook) addAgentSecretsVolToContainers_Vault(vaultConfig vaul } } -func (mw *MutatingWebhook) getVolumes_Vault(existingVolumes []corev1.Volume, agentConfigMapName string, vaultConfig vault.Config) []corev1.Volume { - mw.logger.Debug("Add generic volumes to podspec") +func getServiceAccountMount(containers []corev1.Container, serviceAccountTokenVolumeName string) (serviceAccountMount corev1.VolumeMount) { +mountSearch: + for _, container := range containers { + for _, mount := range container.VolumeMounts { + if mount.MountPath == serviceAccountTokenVolumeName { + serviceAccountMount = mount - volumes := []corev1.Volume{ - { - Name: SecretInitVolumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - Medium: corev1.StorageMediumMemory, - }, + break mountSearch + } + } + } + + return serviceAccountMount +} + +func getBaseSecurityContext(podSecurityContext *corev1.PodSecurityContext, webhookConfig common.Config) *corev1.SecurityContext { + context := &corev1.SecurityContext{ + AllowPrivilegeEscalation: &webhookConfig.PspAllowPrivilegeEscalation, + ReadOnlyRootFilesystem: &webhookConfig.ReadOnlyRootFilesystem, + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{}, + Drop: []corev1.Capability{ + "ALL", }, }, } - if vaultConfig.UseAgent || vaultConfig.CtConfigMap != "" { - mw.logger.Debug("Add vault agent volumes to podspec") - volumes = append(volumes, corev1.Volume{ - Name: "vault-agent-config", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: agentConfigMapName, - }, - }, - }, - }) + if podSecurityContext != nil && podSecurityContext.RunAsUser != nil { + context.RunAsUser = podSecurityContext.RunAsUser } - if vaultConfig.TLSSecret != "" { - mw.logger.Debug("Add vault TLS volume to podspec") + // Although it could explicitly be set to false, + // the behavior of false and unset are the same + if webhookConfig.RunAsNonRoot { + context.RunAsNonRoot = &webhookConfig.RunAsNonRoot + } - volumeName := "vault-tls" - if hasTLSVolume_Vault(existingVolumes) { - volumeName = "secret-init-tls" - } + if webhookConfig.RunAsUser > 0 { + context.RunAsUser = &webhookConfig.RunAsUser + } - volumes = append(volumes, corev1.Volume{ - Name: volumeName, + if webhookConfig.RunAsGroup > 0 { + context.RunAsGroup = &webhookConfig.RunAsGroup + } + + return context +} + +// ======== VAULT ======== + +func (mw *MutatingWebhook) mutatePodForVault(ctx context.Context, pod *corev1.Pod, webhookConfig common.Config, secretInitConfig common.SecretInitConfig, vaultConfig vault.Config, dryRun bool) error { + if isPodAlreadyMutated(pod) { + mw.logger.Info(fmt.Sprintf("Pod %s is already mutated, skipping mutation.", pod.Name)) + return nil + } + + initContainersMutated, err := mw.mutateContainers(ctx, pod.Spec.InitContainers, &pod.Spec, webhookConfig, secretInitConfig, vaultConfig, vaultConfig.ObjectNamespace, vaultConfig.FromPath) + if err != nil { + return err + } + + if initContainersMutated { + mw.logger.Debug("Successfully mutated pod init containers") + } else { + mw.logger.Debug("No pod init containers were mutated") + } + + containersMutated, err := mw.mutateContainers(ctx, pod.Spec.Containers, &pod.Spec, webhookConfig, secretInitConfig, vaultConfig, vaultConfig.ObjectNamespace, vaultConfig.FromPath) + if err != nil { + return err + } + + if containersMutated { + mw.logger.Debug("Successfully mutated pod containers") + } else { + mw.logger.Debug("No pod containers were mutated") + } + + containerEnvVars := []corev1.EnvVar{ + { + Name: "VAULT_ADDR", + Value: vaultConfig.Addr, + }, + { + Name: "VAULT_SKIP_VERIFY", + Value: strconv.FormatBool(vaultConfig.SkipVerify), + }, + } + + if vaultConfig.Token != "" { + containerEnvVars = append(containerEnvVars, corev1.EnvVar{ + Name: "VAULT_TOKEN", + Value: vaultConfig.Token, + }) + } + + containerVolMounts := []corev1.VolumeMount{ + { + Name: SecretInitVolumeName, + MountPath: "/bank-vaults/", + }, + } + if vaultConfig.TLSSecret != "" { + mountPath := "/vault/tls/" + volumeName := "vault-tls" + if hasTLSVolume(pod.Spec.Volumes, volumeName) { + mountPath = "/secret-init/tls/" + volumeName = "secret-init-tls" + } + + containerEnvVars = append(containerEnvVars, corev1.EnvVar{ + Name: "VAULT_CACERT", + Value: mountPath + "ca.crt", + }) + + containerVolMounts = append(containerVolMounts, corev1.VolumeMount{ + Name: volumeName, + MountPath: mountPath, + }) + } + + if vaultConfig.CtConfigMap != "" { + mw.logger.Debug("Consul Template config found") + + mw.addSecretsVolToContainers(pod.Spec.Containers, vaultConfig.ConfigfilePath) + + if vaultConfig.CtShareProcessDefault == "empty" { + mw.logger.Debug("Test our Kubernetes API Version and make the final decision on enabling ShareProcessNamespace") + apiVersion, _ := mw.k8sClient.Discovery().ServerVersion() + versionCompared := kubeVer.CompareKubeAwareVersionStrings("v1.12.0", apiVersion.String()) + mw.logger.Debug(fmt.Sprintf("Kubernetes API version detected: %s", apiVersion.String())) + + if versionCompared >= 0 { + vaultConfig.CtShareProcess = true + } else { + vaultConfig.CtShareProcess = false + } + } + + if vaultConfig.CtShareProcess { + mw.logger.Debug("Detected shared process namespace") + shareProcessNamespace := true + pod.Spec.ShareProcessNamespace = &shareProcessNamespace + } + + if !vaultConfig.CtOnce { + pod.Spec.Containers = append(getContainersForVault(pod.Spec.SecurityContext, webhookConfig, vaultConfig, containerEnvVars, containerVolMounts), pod.Spec.Containers...) + } else { + if vaultConfig.CtInjectInInitcontainers { + mw.addSecretsVolToContainers(pod.Spec.InitContainers, vaultConfig.ConfigfilePath) + } + pod.Spec.InitContainers = append(getContainersForVault(pod.Spec.SecurityContext, webhookConfig, vaultConfig, containerEnvVars, containerVolMounts), pod.Spec.InitContainers...) + } + + mw.logger.Debug("Successfully appended pod containers to spec") + } + + if initContainersMutated || containersMutated || vaultConfig.CtConfigMap != "" || vaultConfig.AgentConfigMap != "" { + var agentConfigMapName string + + if vaultConfig.UseAgent || vaultConfig.CtConfigMap != "" { + if vaultConfig.AgentConfigMap != "" { + agentConfigMapName = vaultConfig.AgentConfigMap + } else { + configMap := getConfigMapForVaultAgent(pod, vaultConfig) + agentConfigMapName = configMap.Name + if !dryRun { + _, err := mw.k8sClient.CoreV1().ConfigMaps(vaultConfig.ObjectNamespace).Create(context.Background(), configMap, metav1.CreateOptions{}) + if err != nil { + if apierrors.IsAlreadyExists(err) { + _, err = mw.k8sClient.CoreV1().ConfigMaps(vaultConfig.ObjectNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{}) + if err != nil { + return errors.WrapIf(err, "failed to update ConfigMap for config") + } + } else { + return errors.WrapIf(err, "failed to create ConfigMap for config") + } + } + } + } + } + + pod.Spec.InitContainers = append(getInitContainersForVault(pod.Spec.Containers, pod.Spec.SecurityContext, webhookConfig, secretInitConfig, vaultConfig, initContainersMutated, containersMutated, containerEnvVars, containerVolMounts), pod.Spec.InitContainers...) + mw.logger.Debug("Successfully appended pod init containers to spec") + + pod.Spec.Volumes = append(pod.Spec.Volumes, mw.getVolumesForVault(pod.Spec.Volumes, agentConfigMapName, vaultConfig)...) + mw.logger.Debug("Successfully appended pod spec volumes") + } + + if vaultConfig.AgentConfigMap != "" && !vaultConfig.UseAgent { + mw.logger.Debug("Vault Agent config found") + + mw.addAgentSecretsVolToContainers(pod.Spec.Containers, vaultConfig.ConfigfilePath) + + if vaultConfig.AgentShareProcessDefault == "empty" { + mw.logger.Debug("Test our Kubernetes API Version and make the final decision on enabling ShareProcessNamespace") + apiVersion, _ := mw.k8sClient.Discovery().ServerVersion() + versionCompared := kubeVer.CompareKubeAwareVersionStrings("v1.12.0", apiVersion.String()) + mw.logger.Debug(fmt.Sprintf("Kubernetes API version detected: %s", apiVersion.String())) + + if versionCompared >= 0 { + vaultConfig.AgentShareProcess = true + } else { + vaultConfig.AgentShareProcess = false + } + } + + if vaultConfig.AgentShareProcess { + mw.logger.Debug("Detected shared process namespace") + shareProcessNamespace := true + pod.Spec.ShareProcessNamespace = &shareProcessNamespace + } + pod.Spec.Containers = append(getAgentContainersForVault(pod.Spec.Containers, pod.Spec.SecurityContext, webhookConfig, vaultConfig, containerEnvVars, containerVolMounts), pod.Spec.Containers...) + + mw.logger.Debug("Successfully appended pod containers to spec") + } + + return nil +} + +func getContainersForVault(podSecurityContext *corev1.PodSecurityContext, webhookConfig common.Config, vaultConfig vault.Config, containerEnvVars []corev1.EnvVar, containerVolMounts []corev1.VolumeMount) []corev1.Container { + containers := []corev1.Container{} + securityContext := getBaseSecurityContext(podSecurityContext, webhookConfig) + + if vaultConfig.CtShareProcess { + securityContext.Capabilities.Add = append(securityContext.Capabilities.Add, "SYS_PTRACE") + } + + containerVolMounts = append(containerVolMounts, corev1.VolumeMount{ + Name: "ct-secrets", + MountPath: vaultConfig.ConfigfilePath, + }, corev1.VolumeMount{ + Name: SecretInitVolumeName, + MountPath: "/home/consul-template", + }, corev1.VolumeMount{ + Name: "ct-configmap", + MountPath: "/vault/ct-config/config.hcl", + ReadOnly: true, + SubPath: "config.hcl", + }) + + var ctCommandString []string + if vaultConfig.CtOnce { + ctCommandString = []string{"-config", "/vault/ct-config/config.hcl", "-once"} + } else { + ctCommandString = []string{"-config", "/vault/ct-config/config.hcl"} + } + + containers = append(containers, corev1.Container{ + Name: "consul-template", + Image: vaultConfig.CtImage, + Args: ctCommandString, + ImagePullPolicy: vaultConfig.CtImagePullPolicy, + SecurityContext: securityContext, + Env: containerEnvVars, + VolumeMounts: containerVolMounts, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: vaultConfig.CtCPU, + corev1.ResourceMemory: vaultConfig.CtMemory, + }, + }, + }) + + return containers +} + +func getConfigMapForVaultAgent(pod *corev1.Pod, vaultConfig vault.Config) *corev1.ConfigMap { + ownerReferences := pod.GetOwnerReferences() + name := pod.GetName() + // If we have no name we are probably part of some controller, + // try to get the name of the owner controller. + if name == "" { + if len(ownerReferences) > 0 { + if strings.Contains(ownerReferences[0].Name, "-") { + generateNameSlice := strings.Split(ownerReferences[0].Name, "-") + name = strings.Join(generateNameSlice[:len(generateNameSlice)-1], "-") + } else { + name = ownerReferences[0].Name + } + } + } + + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name + "-vault-agent-config", + OwnerReferences: ownerReferences, + }, + Data: map[string]string{ + "config.hcl": fmt.Sprintf(vault.AgentConfig, vaultConfig.VaultNamespace, vaultConfig.Path, vaultConfig.Role), + }, + } +} + +func getInitContainersForVault(originalContainers []corev1.Container, podSecurityContext *corev1.PodSecurityContext, webhookConfig common.Config, secretInitConfig common.SecretInitConfig, vaultConfig vault.Config, initContainersMutated bool, containersMutated bool, containerEnvVars []corev1.EnvVar, containerVolMounts []corev1.VolumeMount) []corev1.Container { + containers := []corev1.Container{} + + if vaultConfig.TokenAuthMount != "" { + // vault.security.banzaicloud.io/token-auth-mount: "token:vault-token" + split := strings.Split(vaultConfig.TokenAuthMount, ":") + mountName := split[0] + tokenName := split[1] + fileLoc := "/token/" + tokenName + cmd := fmt.Sprintf("cp %s /vault/.vault-token", fileLoc) + + containers = append(containers, corev1.Container{ + Name: "copy-vault-token", + Image: vaultConfig.AgentImage, + ImagePullPolicy: vaultConfig.AgentImagePullPolicy, + Command: []string{"sh", "-c", cmd}, + SecurityContext: getBaseSecurityContext(podSecurityContext, webhookConfig), + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("50m"), + corev1.ResourceMemory: resource.MustParse("64Mi"), + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: SecretInitVolumeName, + MountPath: "/bank-vaults/", + }, + { + Name: mountName, + MountPath: "/token", + }, + }, + }) + } else if vaultConfig.Token == "" && (vaultConfig.UseAgent || vaultConfig.CtConfigMap != "") { + serviceAccountMount := getServiceAccountMount(originalContainers, vaultConfig.ServiceAccountTokenVolumeName) + + containerVolMounts = append(containerVolMounts, serviceAccountMount, corev1.VolumeMount{ + Name: "vault-agent-config", + MountPath: "/vault/agent/", + }) + + securityContext := getBaseSecurityContext(podSecurityContext, webhookConfig) + securityContext.Capabilities.Add = []corev1.Capability{ + "CHOWN", + "SETFCAP", + "SETGID", + "SETPCAP", + "SETUID", + } + + containers = append(containers, corev1.Container{ + Name: "vault-agent", + Image: vaultConfig.AgentImage, + ImagePullPolicy: vaultConfig.AgentImagePullPolicy, + SecurityContext: securityContext, + Command: []string{"vault", "agent", "-config=/vault/agent/config.hcl", "-exit-after-auth"}, + Env: containerEnvVars, + VolumeMounts: containerVolMounts, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: secretInitConfig.CPULimit, + corev1.ResourceMemory: secretInitConfig.MemoryLimit, + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: secretInitConfig.CPURequest, + corev1.ResourceMemory: secretInitConfig.MemoryRequest, + }, + }, + }) + } + + if initContainersMutated || containersMutated { + containers = append(containers, corev1.Container{ + Name: "copy-secret-init", + Image: secretInitConfig.Image, + ImagePullPolicy: secretInitConfig.ImagePullPolicy, + Command: []string{"sh", "-c", "cp /usr/local/bin/secret-init /bank-vaults/"}, + VolumeMounts: []corev1.VolumeMount{ + { + Name: SecretInitVolumeName, + MountPath: "/bank-vaults/", + }, + }, + + SecurityContext: getBaseSecurityContext(podSecurityContext, webhookConfig), + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: secretInitConfig.CPULimit, + corev1.ResourceMemory: secretInitConfig.MemoryLimit, + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: secretInitConfig.CPURequest, + corev1.ResourceMemory: secretInitConfig.MemoryRequest, + }, + }, + }) + } + + return containers +} + +func (mw *MutatingWebhook) getVolumesForVault(existingVolumes []corev1.Volume, agentConfigMapName string, vaultConfig vault.Config) []corev1.Volume { + mw.logger.Debug("Add generic volumes to podspec") + + volumes := []corev1.Volume{ + { + Name: SecretInitVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: corev1.StorageMediumMemory, + }, + }, + }, + } + + if vaultConfig.UseAgent || vaultConfig.CtConfigMap != "" { + mw.logger.Debug("Add vault agent volumes to podspec") + volumes = append(volumes, corev1.Volume{ + Name: "vault-agent-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: agentConfigMapName, + }, + }, + }, + }) + } + + if vaultConfig.TLSSecret != "" { + mw.logger.Debug("Add vault TLS volume to podspec") + + volumeName := "vault-tls" + if hasTLSVolume(existingVolumes, volumeName) { + volumeName = "secret-init-tls" + } + + volumes = append(volumes, corev1.Volume{ + Name: volumeName, VolumeSource: corev1.VolumeSource{ Projected: &corev1.ProjectedVolumeSource{ Sources: []corev1.VolumeProjection{{ @@ -463,110 +941,399 @@ func (mw *MutatingWebhook) getVolumes_Vault(existingVolumes []corev1.Volume, age if vaultConfig.CtConfigMap != "" { mw.logger.Debug("Add consul template volumes to podspec") - defaultMode := int32(420) - volumes = append(volumes, - corev1.Volume{ - Name: "ct-secrets", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - Medium: corev1.StorageMediumMemory, - }, - }, - }, - corev1.Volume{ - Name: "ct-configmap", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: vaultConfig.CtConfigMap, - }, - DefaultMode: &defaultMode, - Items: []corev1.KeyToPath{ - { - Key: "config.hcl", - Path: "config.hcl", - }, - }, - }, - }, - }) + defaultMode := int32(420) + volumes = append(volumes, + corev1.Volume{ + Name: "ct-secrets", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: corev1.StorageMediumMemory, + }, + }, + }, + corev1.Volume{ + Name: "ct-configmap", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: vaultConfig.CtConfigMap, + }, + DefaultMode: &defaultMode, + Items: []corev1.KeyToPath{ + { + Key: "config.hcl", + Path: "config.hcl", + }, + }, + }, + }, + }) + } + + if vaultConfig.AgentConfigMap != "" { + mw.logger.Debug("Add vault-agent volumes to podspec") + + defaultMode := int32(420) + volumes = append(volumes, + corev1.Volume{ + Name: "agent-secrets", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: corev1.StorageMediumMemory, + }, + }, + }, + corev1.Volume{ + Name: "agent-configmap", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: vaultConfig.AgentConfigMap, + }, + DefaultMode: &defaultMode, + Items: []corev1.KeyToPath{ + { + Key: "config.hcl", + Path: "config.hcl", + }, + }, + }, + }, + }) + } + + return volumes +} + +func getAgentContainersForVault(originalContainers []corev1.Container, podSecurityContext *corev1.PodSecurityContext, webhookConfig common.Config, vaultConfig vault.Config, containerEnvVars []corev1.EnvVar, containerVolMounts []corev1.VolumeMount) []corev1.Container { + containers := []corev1.Container{} + + securityContext := getBaseSecurityContext(podSecurityContext, webhookConfig) + securityContext.Capabilities.Add = []corev1.Capability{ + "CHOWN", + "SETFCAP", + "SETGID", + "SETPCAP", + "SETUID", + "IPC_LOCK", + } + + if vaultConfig.AgentShareProcess { + securityContext.Capabilities.Add = append(securityContext.Capabilities.Add, "SYS_PTRACE") + } + + serviceAccountMount := getServiceAccountMount(originalContainers, vaultConfig.ServiceAccountTokenVolumeName) + + containerVolMounts = append(containerVolMounts, serviceAccountMount, corev1.VolumeMount{ + Name: "agent-secrets", + MountPath: vaultConfig.ConfigfilePath, + }, corev1.VolumeMount{ + Name: "agent-configmap", + MountPath: "/vault/config/config.hcl", + ReadOnly: true, + SubPath: "config.hcl", + }) + + var agentCommandString []string + if vaultConfig.AgentOnce { + agentCommandString = []string{"agent", "-config", "/vault/config/config.hcl", "-exit-after-auth"} + } else { + agentCommandString = []string{"agent", "-config", "/vault/config/config.hcl"} + } + + if vaultConfig.AgentEnvVariables != "" { + var envVars []corev1.EnvVar + err := json.Unmarshal([]byte(vaultConfig.AgentEnvVariables), &envVars) + if err != nil { + envVars = []corev1.EnvVar{} + } + containerEnvVars = append(containerEnvVars, envVars...) + } + + containers = append(containers, corev1.Container{ + Name: "vault-agent", + Image: vaultConfig.AgentImage, + Args: agentCommandString, + ImagePullPolicy: vaultConfig.AgentImagePullPolicy, + SecurityContext: securityContext, + Env: containerEnvVars, + VolumeMounts: containerVolMounts, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: vaultConfig.AgentCPULimit, + corev1.ResourceMemory: vaultConfig.AgentMemoryLimit, + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: vaultConfig.AgentCPURequest, + corev1.ResourceMemory: vaultConfig.AgentMemoryRequest, + }, + }, + }) + + return containers +} + +// ======== BAO ======== + +func (mw *MutatingWebhook) mutatePodForBao(ctx context.Context, pod *corev1.Pod, webhookConfig common.Config, secretInitConfig common.SecretInitConfig, baoConfig bao.Config, dryRun bool) error { + if isPodAlreadyMutated(pod) { + mw.logger.Info(fmt.Sprintf("Pod %s is already mutated, skipping mutation.", pod.Name)) + return nil + } + + initContainersMutated, err := mw.mutateContainers(ctx, pod.Spec.InitContainers, &pod.Spec, webhookConfig, secretInitConfig, baoConfig, baoConfig.ObjectNamespace, baoConfig.FromPath) + if err != nil { + return err + } + + if initContainersMutated { + mw.logger.Debug("Successfully mutated pod init containers") + } else { + mw.logger.Debug("No pod init containers were mutated") + } + + containersMutated, err := mw.mutateContainers(ctx, pod.Spec.Containers, &pod.Spec, webhookConfig, secretInitConfig, baoConfig, baoConfig.ObjectNamespace, baoConfig.FromPath) + if err != nil { + return err + } + + if containersMutated { + mw.logger.Debug("Successfully mutated pod containers") + } else { + mw.logger.Debug("No pod containers were mutated") + } + + containerEnvVars := []corev1.EnvVar{ + { + Name: "BAO_ADDR", + Value: baoConfig.Addr, + }, + { + Name: "BAO_SKIP_VERIFY", + Value: strconv.FormatBool(baoConfig.SkipVerify), + }, + } + + if baoConfig.Token != "" { + containerEnvVars = append(containerEnvVars, corev1.EnvVar{ + Name: "BAO_TOKEN", + Value: baoConfig.Token, + }) + } + + containerVolMounts := []corev1.VolumeMount{ + { + Name: SecretInitVolumeName, + MountPath: "/bank-vaults/", + }, + } + if baoConfig.TLSSecret != "" { + mountPath := "/bao/tls/" + volumeName := "bao-tls" + if hasTLSVolume(pod.Spec.Volumes, volumeName) { + mountPath = "/secret-init/tls/" + volumeName = "secret-init-tls" + } + + containerEnvVars = append(containerEnvVars, corev1.EnvVar{ + Name: "BAO_CACERT", + Value: mountPath + "ca.crt", + }) + + containerVolMounts = append(containerVolMounts, corev1.VolumeMount{ + Name: volumeName, + MountPath: mountPath, + }) + } + + if baoConfig.CtConfigMap != "" { + mw.logger.Debug("Consul Template config found") + + mw.addSecretsVolToContainers(pod.Spec.Containers, baoConfig.ConfigfilePath) + + if baoConfig.CtShareProcessDefault == "empty" { + mw.logger.Debug("Test our Kubernetes API Version and make the final decision on enabling ShareProcessNamespace") + apiVersion, _ := mw.k8sClient.Discovery().ServerVersion() + versionCompared := kubeVer.CompareKubeAwareVersionStrings("v1.12.0", apiVersion.String()) + mw.logger.Debug(fmt.Sprintf("Kubernetes API version detected: %s", apiVersion.String())) + + if versionCompared >= 0 { + baoConfig.CtShareProcess = true + } else { + baoConfig.CtShareProcess = false + } + } + + if baoConfig.CtShareProcess { + mw.logger.Debug("Detected shared process namespace") + shareProcessNamespace := true + pod.Spec.ShareProcessNamespace = &shareProcessNamespace + } + + if !baoConfig.CtOnce { + pod.Spec.Containers = append(getContainersForBao(pod.Spec.SecurityContext, webhookConfig, baoConfig, containerEnvVars, containerVolMounts), pod.Spec.Containers...) + } else { + if baoConfig.CtInjectInInitcontainers { + mw.addSecretsVolToContainers(pod.Spec.InitContainers, baoConfig.ConfigfilePath) + } + pod.Spec.InitContainers = append(getContainersForBao(pod.Spec.SecurityContext, webhookConfig, baoConfig, containerEnvVars, containerVolMounts), pod.Spec.InitContainers...) + } + + mw.logger.Debug("Successfully appended pod containers to spec") } - if vaultConfig.AgentConfigMap != "" { - mw.logger.Debug("Add vault-agent volumes to podspec") + if initContainersMutated || containersMutated || baoConfig.CtConfigMap != "" || baoConfig.AgentConfigMap != "" { + var agentConfigMapName string - defaultMode := int32(420) - volumes = append(volumes, - corev1.Volume{ - Name: "agent-secrets", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - Medium: corev1.StorageMediumMemory, - }, - }, - }, - corev1.Volume{ - Name: "agent-configmap", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: vaultConfig.AgentConfigMap, - }, - DefaultMode: &defaultMode, - Items: []corev1.KeyToPath{ - { - Key: "config.hcl", - Path: "config.hcl", - }, - }, - }, - }, - }) + if baoConfig.UseAgent || baoConfig.CtConfigMap != "" { + if baoConfig.AgentConfigMap != "" { + agentConfigMapName = baoConfig.AgentConfigMap + } else { + configMap := getConfigMapForBaoAgent(pod, baoConfig) + agentConfigMapName = configMap.Name + if !dryRun { + _, err := mw.k8sClient.CoreV1().ConfigMaps(baoConfig.ObjectNamespace).Create(context.Background(), configMap, metav1.CreateOptions{}) + if err != nil { + if apierrors.IsAlreadyExists(err) { + _, err = mw.k8sClient.CoreV1().ConfigMaps(baoConfig.ObjectNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{}) + if err != nil { + return errors.WrapIf(err, "failed to update ConfigMap for config") + } + } else { + return errors.WrapIf(err, "failed to create ConfigMap for config") + } + } + } + } + } + + pod.Spec.InitContainers = append(getInitContainersForBao(pod.Spec.Containers, pod.Spec.SecurityContext, webhookConfig, secretInitConfig, baoConfig, initContainersMutated, containersMutated, containerEnvVars, containerVolMounts), pod.Spec.InitContainers...) + mw.logger.Debug("Successfully appended pod init containers to spec") + + pod.Spec.Volumes = append(pod.Spec.Volumes, mw.getVolumesForBao(pod.Spec.Volumes, agentConfigMapName, baoConfig)...) + mw.logger.Debug("Successfully appended pod spec volumes") } - return volumes -} + if baoConfig.AgentConfigMap != "" && !baoConfig.UseAgent { + mw.logger.Debug("Bao Agent config found") -// If the original Pod contained a Volume "vault-tls", for example Vault instances provisioned by the Operator -// we need to handle that edge case and choose another name for the vault-tls volume for accessing Vault with TLS. -func hasTLSVolume_Vault(volumes []corev1.Volume) bool { - for _, volume := range volumes { - if volume.Name == "vault-tls" { - return true + mw.addAgentSecretsVolToContainers(pod.Spec.Containers, baoConfig.ConfigfilePath) + + if baoConfig.AgentShareProcessDefault == "empty" { + mw.logger.Debug("Test our Kubernetes API Version and make the final decision on enabling ShareProcessNamespace") + apiVersion, _ := mw.k8sClient.Discovery().ServerVersion() + versionCompared := kubeVer.CompareKubeAwareVersionStrings("v1.12.0", apiVersion.String()) + mw.logger.Debug(fmt.Sprintf("Kubernetes API version detected: %s", apiVersion.String())) + + if versionCompared >= 0 { + baoConfig.AgentShareProcess = true + } else { + baoConfig.AgentShareProcess = false + } + } + + if baoConfig.AgentShareProcess { + mw.logger.Debug("Detected shared process namespace") + shareProcessNamespace := true + pod.Spec.ShareProcessNamespace = &shareProcessNamespace } + pod.Spec.Containers = append(getAgentContainersForBao(pod.Spec.Containers, pod.Spec.SecurityContext, webhookConfig, baoConfig, containerEnvVars, containerVolMounts), pod.Spec.Containers...) + + mw.logger.Debug("Successfully appended pod containers to spec") } - return false + + return nil } -func getServiceAccountMount_Vault(containers []corev1.Container, vaultConfig vault.Config) (serviceAccountMount corev1.VolumeMount) { -mountSearch: - for _, container := range containers { - for _, mount := range container.VolumeMounts { - if mount.MountPath == vaultConfig.ServiceAccountTokenVolumeName { - serviceAccountMount = mount +func getContainersForBao(podSecurityContext *corev1.PodSecurityContext, webhookConfig common.Config, baoConfig bao.Config, containerEnvVars []corev1.EnvVar, containerVolMounts []corev1.VolumeMount) []corev1.Container { + containers := []corev1.Container{} + securityContext := getBaseSecurityContext(podSecurityContext, webhookConfig) - break mountSearch + if baoConfig.CtShareProcess { + securityContext.Capabilities.Add = append(securityContext.Capabilities.Add, "SYS_PTRACE") + } + + containerVolMounts = append(containerVolMounts, corev1.VolumeMount{ + Name: "ct-secrets", + MountPath: baoConfig.ConfigfilePath, + }, corev1.VolumeMount{ + Name: SecretInitVolumeName, + MountPath: "/home/consul-template", + }, corev1.VolumeMount{ + Name: "ct-configmap", + MountPath: "/bao/ct-config/config.hcl", + ReadOnly: true, + SubPath: "config.hcl", + }) + + var ctCommandString []string + if baoConfig.CtOnce { + ctCommandString = []string{"-config", "/bao/ct-config/config.hcl", "-once"} + } else { + ctCommandString = []string{"-config", "/bao/ct-config/config.hcl"} + } + + containers = append(containers, corev1.Container{ + Name: "consul-template", + Image: baoConfig.CtImage, + Args: ctCommandString, + ImagePullPolicy: baoConfig.CtImagePullPolicy, + SecurityContext: securityContext, + Env: containerEnvVars, + VolumeMounts: containerVolMounts, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: baoConfig.CtCPU, + corev1.ResourceMemory: baoConfig.CtMemory, + }, + }, + }) + + return containers +} + +func getConfigMapForBaoAgent(pod *corev1.Pod, baoConfig bao.Config) *corev1.ConfigMap { + ownerReferences := pod.GetOwnerReferences() + name := pod.GetName() + // If we have no name we are probably part of some controller, + // try to get the name of the owner controller. + if name == "" { + if len(ownerReferences) > 0 { + if strings.Contains(ownerReferences[0].Name, "-") { + generateNameSlice := strings.Split(ownerReferences[0].Name, "-") + name = strings.Join(generateNameSlice[:len(generateNameSlice)-1], "-") + } else { + name = ownerReferences[0].Name } } } - return serviceAccountMount + + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name + "-bao-agent-config", + OwnerReferences: ownerReferences, + }, + Data: map[string]string{ + "config.hcl": fmt.Sprintf(bao.AgentConfig, baoConfig.BaoNamespace, baoConfig.Path, baoConfig.Role), + }, + } } -func getInitContainers_Vault(originalContainers []corev1.Container, podSecurityContext *corev1.PodSecurityContext, webhookConfig common.Config, secretInitConfig common.SecretInitConfig, vaultConfig vault.Config, initContainersMutated bool, containersMutated bool, containerEnvVars []corev1.EnvVar, containerVolMounts []corev1.VolumeMount) []corev1.Container { +func getInitContainersForBao(originalContainers []corev1.Container, podSecurityContext *corev1.PodSecurityContext, webhookConfig common.Config, secretInitConfig common.SecretInitConfig, baoConfig bao.Config, initContainersMutated bool, containersMutated bool, containerEnvVars []corev1.EnvVar, containerVolMounts []corev1.VolumeMount) []corev1.Container { containers := []corev1.Container{} - if vaultConfig.TokenAuthMount != "" { - // vault.security.banzaicloud.io/token-auth-mount: "token:vault-token" - split := strings.Split(vaultConfig.TokenAuthMount, ":") + if baoConfig.TokenAuthMount != "" { + // bao.security.banzaicloud.io/bao-token-auth-mount: "token:bao-token" + split := strings.Split(baoConfig.TokenAuthMount, ":") mountName := split[0] tokenName := split[1] fileLoc := "/token/" + tokenName - cmd := fmt.Sprintf("cp %s /vault/.vault-token", fileLoc) + cmd := fmt.Sprintf("cp %s /bao/.bao-token", fileLoc) containers = append(containers, corev1.Container{ - Name: "copy-vault-token", - Image: vaultConfig.AgentImage, - ImagePullPolicy: vaultConfig.AgentImagePullPolicy, + Name: "copy-bao-token", + Image: baoConfig.AgentImage, + ImagePullPolicy: baoConfig.AgentImagePullPolicy, Command: []string{"sh", "-c", cmd}, SecurityContext: getBaseSecurityContext(podSecurityContext, webhookConfig), Resources: corev1.ResourceRequirements{ @@ -586,12 +1353,12 @@ func getInitContainers_Vault(originalContainers []corev1.Container, podSecurityC }, }, }) - } else if vaultConfig.Token == "" && (vaultConfig.UseAgent || vaultConfig.CtConfigMap != "") { - serviceAccountMount := getServiceAccountMount_Vault(originalContainers, vaultConfig) + } else if baoConfig.Token == "" && (baoConfig.UseAgent || baoConfig.CtConfigMap != "") { + serviceAccountMount := getServiceAccountMount(originalContainers, baoConfig.ServiceAccountTokenVolumeName) containerVolMounts = append(containerVolMounts, serviceAccountMount, corev1.VolumeMount{ - Name: "vault-agent-config", - MountPath: "/vault/agent/", + Name: "bao-agent-config", + MountPath: "/bao/agent/", }) securityContext := getBaseSecurityContext(podSecurityContext, webhookConfig) @@ -604,11 +1371,11 @@ func getInitContainers_Vault(originalContainers []corev1.Container, podSecurityC } containers = append(containers, corev1.Container{ - Name: "vault-agent", - Image: vaultConfig.AgentImage, - ImagePullPolicy: vaultConfig.AgentImagePullPolicy, + Name: "bao-agent", + Image: baoConfig.AgentImage, + ImagePullPolicy: baoConfig.AgentImagePullPolicy, SecurityContext: securityContext, - Command: []string{"vault", "agent", "-config=/vault/agent/config.hcl", "-exit-after-auth"}, + Command: []string{"bao", "agent", "-config=/bao/agent/config.hcl", "-exit-after-auth"}, Env: containerEnvVars, VolumeMounts: containerVolMounts, Resources: corev1.ResourceRequirements{ @@ -654,55 +1421,129 @@ func getInitContainers_Vault(originalContainers []corev1.Container, podSecurityC return containers } -func getContainers_Vault(podSecurityContext *corev1.PodSecurityContext, webhookConfig common.Config, vaultConfig vault.Config, containerEnvVars []corev1.EnvVar, containerVolMounts []corev1.VolumeMount) []corev1.Container { - containers := []corev1.Container{} - securityContext := getBaseSecurityContext(podSecurityContext, webhookConfig) +func (mw *MutatingWebhook) getVolumesForBao(existingVolumes []corev1.Volume, agentConfigMapName string, baoConfig bao.Config) []corev1.Volume { + mw.logger.Debug("Add generic volumes to podspec") - if vaultConfig.CtShareProcess { - securityContext.Capabilities.Add = append(securityContext.Capabilities.Add, "SYS_PTRACE") + volumes := []corev1.Volume{ + { + Name: SecretInitVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: corev1.StorageMediumMemory, + }, + }, + }, } - containerVolMounts = append(containerVolMounts, corev1.VolumeMount{ - Name: "ct-secrets", - MountPath: vaultConfig.ConfigfilePath, - }, corev1.VolumeMount{ - Name: SecretInitVolumeName, - MountPath: "/home/consul-template", - }, corev1.VolumeMount{ - Name: "ct-configmap", - MountPath: "/vault/ct-config/config.hcl", - ReadOnly: true, - SubPath: "config.hcl", - }, - ) + if baoConfig.UseAgent || baoConfig.CtConfigMap != "" { + mw.logger.Debug("Add bao agent volumes to podspec") + volumes = append(volumes, corev1.Volume{ + Name: "bao-agent-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: agentConfigMapName, + }, + }, + }, + }) + } - var ctCommandString []string - if vaultConfig.CtOnce { - ctCommandString = []string{"-config", "/vault/ct-config/config.hcl", "-once"} - } else { - ctCommandString = []string{"-config", "/vault/ct-config/config.hcl"} + if baoConfig.TLSSecret != "" { + mw.logger.Debug("Add bao TLS volume to podspec") + + volumeName := "bao-tls" + if hasTLSVolume(existingVolumes, volumeName) { + volumeName = "secret-init-tls" + } + + volumes = append(volumes, corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{{ + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: baoConfig.TLSSecret, + }, + Items: []corev1.KeyToPath{{ + Key: "ca.crt", + Path: "ca.crt", + }}, + }, + }}, + }, + }, + }) + } + if baoConfig.CtConfigMap != "" { + mw.logger.Debug("Add consul template volumes to podspec") + + defaultMode := int32(420) + volumes = append(volumes, + corev1.Volume{ + Name: "ct-secrets", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: corev1.StorageMediumMemory, + }, + }, + }, + corev1.Volume{ + Name: "ct-configmap", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: baoConfig.CtConfigMap, + }, + DefaultMode: &defaultMode, + Items: []corev1.KeyToPath{ + { + Key: "config.hcl", + Path: "config.hcl", + }, + }, + }, + }, + }) } - containers = append(containers, corev1.Container{ - Name: "consul-template", - Image: vaultConfig.CtImage, - Args: ctCommandString, - ImagePullPolicy: vaultConfig.CtImagePullPolicy, - SecurityContext: securityContext, - Env: containerEnvVars, - VolumeMounts: containerVolMounts, - Resources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: vaultConfig.CtCPU, - corev1.ResourceMemory: vaultConfig.CtMemory, + if baoConfig.AgentConfigMap != "" { + mw.logger.Debug("Add bao-agent volumes to podspec") + + defaultMode := int32(420) + volumes = append(volumes, + corev1.Volume{ + Name: "agent-secrets", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: corev1.StorageMediumMemory, + }, + }, }, - }, - }) + corev1.Volume{ + Name: "agent-configmap", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: baoConfig.AgentConfigMap, + }, + DefaultMode: &defaultMode, + Items: []corev1.KeyToPath{ + { + Key: "config.hcl", + Path: "config.hcl", + }, + }, + }, + }, + }) + } - return containers + return volumes } -func getAgentContainers_Vault(originalContainers []corev1.Container, podSecurityContext *corev1.PodSecurityContext, webhookConfig common.Config, vaultConfig vault.Config, containerEnvVars []corev1.EnvVar, containerVolMounts []corev1.VolumeMount) []corev1.Container { +func getAgentContainersForBao(originalContainers []corev1.Container, podSecurityContext *corev1.PodSecurityContext, webhookConfig common.Config, baoConfig bao.Config, containerEnvVars []corev1.EnvVar, containerVolMounts []corev1.VolumeMount) []corev1.Container { containers := []corev1.Container{} securityContext := getBaseSecurityContext(podSecurityContext, webhookConfig) @@ -715,33 +1556,32 @@ func getAgentContainers_Vault(originalContainers []corev1.Container, podSecurity "IPC_LOCK", } - if vaultConfig.AgentShareProcess { + if baoConfig.AgentShareProcess { securityContext.Capabilities.Add = append(securityContext.Capabilities.Add, "SYS_PTRACE") } - serviceAccountMount := getServiceAccountMount_Vault(originalContainers, vaultConfig) + serviceAccountMount := getServiceAccountMount(originalContainers, baoConfig.ServiceAccountTokenVolumeName) containerVolMounts = append(containerVolMounts, serviceAccountMount, corev1.VolumeMount{ Name: "agent-secrets", - MountPath: vaultConfig.ConfigfilePath, + MountPath: baoConfig.ConfigfilePath, }, corev1.VolumeMount{ Name: "agent-configmap", - MountPath: "/vault/config/config.hcl", + MountPath: "/bao/config/config.hcl", ReadOnly: true, SubPath: "config.hcl", - }, - ) + }) var agentCommandString []string - if vaultConfig.AgentOnce { - agentCommandString = []string{"agent", "-config", "/vault/config/config.hcl", "-exit-after-auth"} + if baoConfig.AgentOnce { + agentCommandString = []string{"agent", "-config", "/bao/config/config.hcl", "-exit-after-auth"} } else { - agentCommandString = []string{"agent", "-config", "/vault/config/config.hcl"} + agentCommandString = []string{"agent", "-config", "/bao/config/config.hcl"} } - if vaultConfig.AgentEnvVariables != "" { + if baoConfig.AgentEnvVariables != "" { var envVars []corev1.EnvVar - err := json.Unmarshal([]byte(vaultConfig.AgentEnvVariables), &envVars) + err := json.Unmarshal([]byte(baoConfig.AgentEnvVariables), &envVars) if err != nil { envVars = []corev1.EnvVar{} } @@ -749,220 +1589,24 @@ func getAgentContainers_Vault(originalContainers []corev1.Container, podSecurity } containers = append(containers, corev1.Container{ - Name: "vault-agent", - Image: vaultConfig.AgentImage, + Name: "bao-agent", + Image: baoConfig.AgentImage, Args: agentCommandString, - ImagePullPolicy: vaultConfig.AgentImagePullPolicy, + ImagePullPolicy: baoConfig.AgentImagePullPolicy, SecurityContext: securityContext, Env: containerEnvVars, VolumeMounts: containerVolMounts, Resources: corev1.ResourceRequirements{ Limits: corev1.ResourceList{ - corev1.ResourceCPU: vaultConfig.AgentCPULimit, - corev1.ResourceMemory: vaultConfig.AgentMemoryLimit, + corev1.ResourceCPU: baoConfig.AgentCPULimit, + corev1.ResourceMemory: baoConfig.AgentMemoryLimit, }, Requests: corev1.ResourceList{ - corev1.ResourceCPU: vaultConfig.AgentCPURequest, - corev1.ResourceMemory: vaultConfig.AgentMemoryRequest, + corev1.ResourceCPU: baoConfig.AgentCPURequest, + corev1.ResourceMemory: baoConfig.AgentMemoryRequest, }, }, }) return containers } - -func getConfigMapForVaultAgent_Vault(pod *corev1.Pod, vaultConfig vault.Config) *corev1.ConfigMap { - ownerReferences := pod.GetOwnerReferences() - name := pod.GetName() - // If we have no name we are probably part of some controller, - // try to get the name of the owner controller. - if name == "" { - if len(ownerReferences) > 0 { - if strings.Contains(ownerReferences[0].Name, "-") { - generateNameSlice := strings.Split(ownerReferences[0].Name, "-") - name = strings.Join(generateNameSlice[:len(generateNameSlice)-1], "-") - } else { - name = ownerReferences[0].Name - } - } - } - return &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: name + "-vault-agent-config", - OwnerReferences: ownerReferences, - }, - Data: map[string]string{ - "config.hcl": fmt.Sprintf(vaultAgentConfig, vaultConfig.VaultNamespace, vaultConfig.Path, vaultConfig.Role), - }, - } -} - -// ======== VAULT ======== - -func (mw *MutatingWebhook) mutatePodForVault(ctx context.Context, pod *corev1.Pod, webhookConfig common.Config, secretInitConfig common.SecretInitConfig, vaultConfig vault.Config, dryRun bool) error { - if isPodAlreadyMutated(pod) { - mw.logger.Info(fmt.Sprintf("Pod %s is already mutated, skipping mutation.", pod.Name)) - return nil - } - - initContainersMutated, err := mw.mutateContainers(ctx, pod.Spec.InitContainers, &pod.Spec, webhookConfig, secretInitConfig, vaultConfig) - if err != nil { - return err - } - - if initContainersMutated { - mw.logger.Debug("Successfully mutated pod init containers") - } else { - mw.logger.Debug("No pod init containers were mutated") - } - - containersMutated, err := mw.mutateContainers(ctx, pod.Spec.Containers, &pod.Spec, webhookConfig, secretInitConfig, vaultConfig) - if err != nil { - return err - } - - if containersMutated { - mw.logger.Debug("Successfully mutated pod containers") - } else { - mw.logger.Debug("No pod containers were mutated") - } - - containerEnvVars := []corev1.EnvVar{ - { - Name: "VAULT_ADDR", - Value: vaultConfig.Addr, - }, - { - Name: "VAULT_SKIP_VERIFY", - Value: strconv.FormatBool(vaultConfig.SkipVerify), - }, - } - - if vaultConfig.Token != "" { - containerEnvVars = append(containerEnvVars, corev1.EnvVar{ - Name: "VAULT_TOKEN", - Value: vaultConfig.Token, - }) - } - - containerVolMounts := []corev1.VolumeMount{ - { - Name: SecretInitVolumeName, - MountPath: "/bank-vaults/", - }, - } - if vaultConfig.TLSSecret != "" { - mountPath := "/vault/tls/" - volumeName := "vault-tls" - if hasTLSVolume_Vault(pod.Spec.Volumes) { - mountPath = "/secret-init/tls/" - volumeName = "secret-init-tls" - } - - containerEnvVars = append(containerEnvVars, corev1.EnvVar{ - Name: "VAULT_CACERT", - Value: mountPath + "ca.crt", - }) - containerVolMounts = append(containerVolMounts, corev1.VolumeMount{ - Name: volumeName, - MountPath: mountPath, - }) - } - - if vaultConfig.CtConfigMap != "" { - mw.logger.Debug("Consul Template config found") - - mw.addSecretsVolToContainers_Vault(vaultConfig, pod.Spec.Containers) - - if vaultConfig.CtShareProcessDefault == "empty" { - mw.logger.Debug("Test our Kubernetes API Version and make the final decision on enabling ShareProcessNamespace") - apiVersion, _ := mw.k8sClient.Discovery().ServerVersion() - versionCompared := kubeVer.CompareKubeAwareVersionStrings("v1.12.0", apiVersion.String()) - mw.logger.Debug(fmt.Sprintf("Kubernetes API version detected: %s", apiVersion.String())) - - if versionCompared >= 0 { - vaultConfig.CtShareProcess = true - } else { - vaultConfig.CtShareProcess = false - } - } - - if vaultConfig.CtShareProcess { - mw.logger.Debug("Detected shared process namespace") - shareProcessNamespace := true - pod.Spec.ShareProcessNamespace = &shareProcessNamespace - } - if !vaultConfig.CtOnce { - pod.Spec.Containers = append(getContainers_Vault(pod.Spec.SecurityContext, webhookConfig, vaultConfig, containerEnvVars, containerVolMounts), pod.Spec.Containers...) - } else { - if vaultConfig.CtInjectInInitcontainers { - mw.addSecretsVolToContainers_Vault(vaultConfig, pod.Spec.InitContainers) - } - pod.Spec.InitContainers = append(getContainers_Vault(pod.Spec.SecurityContext, webhookConfig, vaultConfig, containerEnvVars, containerVolMounts), pod.Spec.InitContainers...) - } - - mw.logger.Debug("Successfully appended pod containers to spec") - } - - if initContainersMutated || containersMutated || vaultConfig.CtConfigMap != "" || vaultConfig.AgentConfigMap != "" { - var agentConfigMapName string - - if vaultConfig.UseAgent || vaultConfig.CtConfigMap != "" { - if vaultConfig.AgentConfigMap != "" { - agentConfigMapName = vaultConfig.AgentConfigMap - } else { - configMap := getConfigMapForVaultAgent_Vault(pod, vaultConfig) - agentConfigMapName = configMap.Name - if !dryRun { - _, err := mw.k8sClient.CoreV1().ConfigMaps(vaultConfig.ObjectNamespace).Create(context.Background(), configMap, metav1.CreateOptions{}) - if err != nil { - if apierrors.IsAlreadyExists(err) { - _, err = mw.k8sClient.CoreV1().ConfigMaps(vaultConfig.ObjectNamespace).Update(context.Background(), configMap, metav1.UpdateOptions{}) - if err != nil { - return errors.WrapIf(err, "failed to update ConfigMap for config") - } - } else { - return errors.WrapIf(err, "failed to create ConfigMap for config") - } - } - } - } - } - - pod.Spec.InitContainers = append(getInitContainers_Vault(pod.Spec.Containers, pod.Spec.SecurityContext, webhookConfig, secretInitConfig, vaultConfig, initContainersMutated, containersMutated, containerEnvVars, containerVolMounts), pod.Spec.InitContainers...) - mw.logger.Debug("Successfully appended pod init containers to spec") - - pod.Spec.Volumes = append(pod.Spec.Volumes, mw.getVolumes_Vault(pod.Spec.Volumes, agentConfigMapName, vaultConfig)...) - mw.logger.Debug("Successfully appended pod spec volumes") - } - - if vaultConfig.AgentConfigMap != "" && !vaultConfig.UseAgent { - mw.logger.Debug("Vault Agent config found") - - mw.addAgentSecretsVolToContainers_Vault(vaultConfig, pod.Spec.Containers) - - if vaultConfig.AgentShareProcessDefault == "empty" { - mw.logger.Debug("Test our Kubernetes API Version and make the final decision on enabling ShareProcessNamespace") - apiVersion, _ := mw.k8sClient.Discovery().ServerVersion() - versionCompared := kubeVer.CompareKubeAwareVersionStrings("v1.12.0", apiVersion.String()) - mw.logger.Debug(fmt.Sprintf("Kubernetes API version detected: %s", apiVersion.String())) - - if versionCompared >= 0 { - vaultConfig.AgentShareProcess = true - } else { - vaultConfig.AgentShareProcess = false - } - } - - if vaultConfig.AgentShareProcess { - mw.logger.Debug("Detected shared process namespace") - shareProcessNamespace := true - pod.Spec.ShareProcessNamespace = &shareProcessNamespace - } - pod.Spec.Containers = append(getAgentContainers_Vault(pod.Spec.Containers, pod.Spec.SecurityContext, webhookConfig, vaultConfig, containerEnvVars, containerVolMounts), pod.Spec.Containers...) - - mw.logger.Debug("Successfully appended pod containers to spec") - } - - return nil -} diff --git a/pkg/webhook/pod_test.go b/pkg/webhook/pod_test.go index 54ca986..a1dc969 100644 --- a/pkg/webhook/pod_test.go +++ b/pkg/webhook/pod_test.go @@ -30,6 +30,7 @@ import ( fake "k8s.io/client-go/kubernetes/fake" "github.com/bank-vaults/secrets-webhook/pkg/common" + "github.com/bank-vaults/secrets-webhook/pkg/provider/bao" "github.com/bank-vaults/secrets-webhook/pkg/provider/vault" ) @@ -55,6 +56,16 @@ var ( Passthrough: "vaultPassthrough", ClientTimeout: 10 * time.Second, }, + bao.ProviderName: bao.Config{ + Addr: "addr", + SkipVerify: false, + Path: "path", + Role: "role", + AuthMethod: "jwt", + IgnoreMissingSecrets: "ignoreMissingSecrets", + Passthrough: "vaultPassthrough", + ClientTimeout: 10 * time.Second, + }, } ) @@ -74,6 +85,7 @@ func Test_mutatingWebhook_mutateContainers_Vault(t *testing.T) { k8sClient kubernetes.Interface registry ImageRegistry } + type args struct { containers []corev1.Container podSpec *corev1.PodSpec @@ -81,6 +93,7 @@ func Test_mutatingWebhook_mutateContainers_Vault(t *testing.T) { SecretInitConfig common.SecretInitConfig vaultConfig vault.Config } + tests := []struct { name string fields fields @@ -578,7 +591,7 @@ func Test_mutatingWebhook_mutateContainers_Vault(t *testing.T) { currentlyUsedProvider = vault.ProviderName - got, err := mw.mutateContainers(context.Background(), ttp.args.containers, ttp.args.podSpec, ttp.args.webhookConfig, ttp.args.SecretInitConfig, ttp.args.vaultConfig) + got, err := mw.mutateContainers(context.Background(), ttp.args.containers, ttp.args.podSpec, ttp.args.webhookConfig, ttp.args.SecretInitConfig, ttp.args.vaultConfig, ttp.args.vaultConfig.ObjectNamespace, ttp.args.vaultConfig.FromPath) if (err != nil) != ttp.wantErr { t.Errorf("MutatingWebhook.mutateContainers() error = %v, wantErr %v", err, ttp.wantErr) return @@ -600,6 +613,7 @@ func Test_mutatingWebhook_mutatePod(t *testing.T) { k8sClient kubernetes.Interface registry ImageRegistry } + type args struct { pod *corev1.Pod webhookConfig common.Config diff --git a/pkg/webhook/webhook.go b/pkg/webhook/webhook.go index 7371b1a..b8113a6 100644 --- a/pkg/webhook/webhook.go +++ b/pkg/webhook/webhook.go @@ -373,7 +373,7 @@ func (mw *MutatingWebhook) newVaultClient(vaultConfig vaultprov.Config) (*vault. vault.ClientRole(vaultConfig.Role), vault.ClientAuthPath(vaultConfig.Path), vault.NamespacedSecretAuthMethod, - vault.ClientLogger(&clientLogger{logger: mw.logger}), + vault.ClientLogger(&vaultprov.ClientLogger{Logger: mw.logger}), vault.ExistingSecret(saToken), vault.VaultNamespace(vaultConfig.VaultNamespace), ) @@ -384,7 +384,7 @@ func (mw *MutatingWebhook) newVaultClient(vaultConfig vaultprov.Config) (*vault. vault.ClientRole(vaultConfig.Role), vault.ClientAuthPath(vaultConfig.Path), vault.ClientAuthMethod(vaultConfig.AuthMethod), - vault.ClientLogger(&clientLogger{logger: mw.logger}), + vault.ClientLogger(&vaultprov.ClientLogger{Logger: mw.logger}), vault.VaultNamespace(vaultConfig.VaultNamespace), ) } @@ -468,7 +468,7 @@ func (mw *MutatingWebhook) newBaoClient(baoConfig baoprov.Config) (*bao.Client, bao.ClientRole(baoConfig.Role), bao.ClientAuthPath(baoConfig.Path), bao.NamespacedSecretAuthMethod, - bao.ClientLogger(&clientLogger{logger: mw.logger}), + bao.ClientLogger(&baoprov.ClientLogger{Logger: mw.logger}), bao.ExistingSecret(saToken), bao.VaultNamespace(baoConfig.BaoNamespace), ) @@ -479,7 +479,7 @@ func (mw *MutatingWebhook) newBaoClient(baoConfig baoprov.Config) (*bao.Client, bao.ClientRole(baoConfig.Role), bao.ClientAuthPath(baoConfig.Path), bao.ClientAuthMethod(baoConfig.AuthMethod), - bao.ClientLogger(&clientLogger{logger: mw.logger}), + bao.ClientLogger(&baoprov.ClientLogger{Logger: mw.logger}), bao.VaultNamespace(baoConfig.BaoNamespace), ) }