From 654386a635a9f41627f758a4f914a3086c95187a Mon Sep 17 00:00:00 2001 From: Utkarsh Chanchlani Date: Tue, 16 Jan 2024 19:07:56 -0800 Subject: [PATCH] Auth config block supports common arguments from env and flags #577 Also fixes using AWS IRSA token by mistake if both included in the pod's volume #544 This maybe a better fix then the proposed #545 pull request as this is likely more future-proof to other third party k8s provider launching their own Service Account Token injection, assuming the third party k8s provider will follow the unsaid convention of injecting the token in the /serviceaccount/token path --- CHANGELOG.md | 6 ++ agent-inject/agent/agent.go | 3 +- agent-inject/agent/agent_test.go | 61 +++++++++++++++++++- agent-inject/agent/annotations.go | 9 +++ agent-inject/agent/annotations_test.go | 6 +- agent-inject/agent/container_sidecar_test.go | 8 +-- agent-inject/handler.go | 2 + agent-inject/handler_test.go | 4 +- subcommand/injector/command.go | 23 ++++++++ subcommand/injector/flags.go | 7 +++ subcommand/injector/flags_test.go | 1 + 11 files changed, 118 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61332ae2..7cb303a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ Changes: * `github.com/hashicorp/go-secure-stdlib/tlsutil` v0.1.2 => v0.1.3 * `github.com/prometheus/client_golang` v1.17.0 => v1.18.0 +Improvements: +* Auth config block can support common arguments from env and flags [GH-577](https://github.com/hashicorp/vault-k8s/pull/577) + +Bugs: +* Prevent incorrect k8s serviceaccount volume mount to be picked when multiple serviceaccount volumes are present [GH-577](https://github.com/hashicorp/vault-k8s/pull/577) + ## 1.3.1 (October 25, 2023) Changes: diff --git a/agent-inject/agent/agent.go b/agent-inject/agent/agent.go index 3ecbdb07..81e461f7 100644 --- a/agent-inject/agent/agent.go +++ b/agent-inject/agent/agent.go @@ -780,7 +780,8 @@ func serviceaccount(pod *corev1.Pod) (*ServiceAccountTokenVolume, error) { // Fallback to searching for normal service account token for _, container := range pod.Spec.Containers { for _, volumes := range container.VolumeMounts { - if strings.Contains(volumes.MountPath, "serviceaccount") { + // Must find only kubernetes serviceaccount token + if strings.Contains(volumes.MountPath, "kubernetes.io/serviceaccount") { return &ServiceAccountTokenVolume{ Name: volumes.Name, MountPath: volumes.MountPath, diff --git a/agent-inject/agent/agent_test.go b/agent-inject/agent/agent_test.go index f137c6d9..24f4974f 100644 --- a/agent-inject/agent/agent_test.go +++ b/agent-inject/agent/agent_test.go @@ -25,7 +25,7 @@ func testPod(annotations map[string]string) *corev1.Pod { VolumeMounts: []corev1.VolumeMount{ { Name: "foobar", - MountPath: "serviceaccount/somewhere", + MountPath: "kubernetes.io/serviceaccount/somewhere", }, { Name: "tobecopied", @@ -70,7 +70,7 @@ func testPodIRSA(annotations map[string]string) *corev1.Pod { VolumeMounts: []corev1.VolumeMount{ { Name: "foobar", - MountPath: "serviceaccount/somewhere", + MountPath: "kubernetes.io/serviceaccount/somewhere", }, }, }, @@ -564,6 +564,63 @@ func Test_serviceaccount(t *testing.T) { }, }, }, + "multiple service account tokens from different sources": { + expected: &ServiceAccountTokenVolume{ + Name: "kube-api-access-4bfzq", + MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", + TokenPath: "token", + }, + expectedError: "", + pod: &corev1.Pod{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + VolumeMounts: []corev1.VolumeMount{ + { + Name: "aws-iam-token", + MountPath: "/var/run/secrets/eks.amazonaws.com/serviceaccount", + }, + { + Name: "kube-api-access-4bfzq", + MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "aws-iam-token", + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + ServiceAccountToken: &corev1.ServiceAccountTokenProjection{ + Path: "token", + Audience: "sts.amazonaws.com", + }, + }, + }, + }, + }, + }, + { + Name: "kube-api-access-4bfzq", + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + ServiceAccountToken: &corev1.ServiceAccountTokenProjection{ + Path: "token", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/agent-inject/agent/annotations.go b/agent-inject/agent/annotations.go index f6b8b9f3..c26f9a48 100644 --- a/agent-inject/agent/annotations.go +++ b/agent-inject/agent/annotations.go @@ -317,6 +317,7 @@ type AgentConfig struct { Address string AuthType string AuthPath string + AuthConfigExtraArgs map[string]string VaultNamespace string Namespace string RevokeOnShutdown bool @@ -385,6 +386,14 @@ func Init(pod *corev1.Pod, cfg AgentConfig) error { pod.ObjectMeta.Annotations[AnnotationVaultAuthPath] = cfg.AuthPath } + // Set auth config extra args to annotations + for key, val := range cfg.AuthConfigExtraArgs { + annotationKey := fmt.Sprintf("%s-%s", AnnotationVaultAuthConfig, key) + if _, ok := pod.ObjectMeta.Annotations[annotationKey]; !ok { + pod.ObjectMeta.Annotations[annotationKey] = val + } + } + if _, ok := pod.ObjectMeta.Annotations[AnnotationVaultNamespace]; !ok { pod.ObjectMeta.Annotations[AnnotationVaultNamespace] = cfg.VaultNamespace } diff --git a/agent-inject/agent/annotations_test.go b/agent-inject/agent/annotations_test.go index a3806e44..ff71acff 100644 --- a/agent-inject/agent/annotations_test.go +++ b/agent-inject/agent/annotations_test.go @@ -973,7 +973,7 @@ func TestAuthConfigAnnotations(t *testing.T) { }, map[string]interface{}{ "role": "backwardscompat", - "token_path": "serviceaccount/somewhere/token", + "token_path": "kubernetes.io/serviceaccount/somewhere/token", }, }, { @@ -983,7 +983,7 @@ func TestAuthConfigAnnotations(t *testing.T) { }, map[string]interface{}{ "role": "backwardscompat", - "token_path": "serviceaccount/somewhere/token", + "token_path": "kubernetes.io/serviceaccount/somewhere/token", }, }, { @@ -1008,7 +1008,7 @@ func TestAuthConfigAnnotations(t *testing.T) { "client_cert": "baz", "credential_poll_interval": "1", // string->int conversion left up to consuming app HCL parser "remove_secret_id_file_after_reading": "false", // string->bool, same as above - "token_path": "serviceaccount/somewhere/token", + "token_path": "kubernetes.io/serviceaccount/somewhere/token", }, }, } diff --git a/agent-inject/agent/container_sidecar_test.go b/agent-inject/agent/container_sidecar_test.go index de9801b7..3cf6c621 100644 --- a/agent-inject/agent/container_sidecar_test.go +++ b/agent-inject/agent/container_sidecar_test.go @@ -1289,14 +1289,14 @@ func TestAgentJsonPatch(t *testing.T) { baseContainerEnvVars, corev1.EnvVar{Name: "VAULT_LOG_LEVEL", Value: "info"}, corev1.EnvVar{Name: "VAULT_LOG_FORMAT", Value: "standard"}, - corev1.EnvVar{Name: "VAULT_CONFIG", Value: "eyJhdXRvX2F1dGgiOnsibWV0aG9kIjp7InR5cGUiOiJrdWJlcm5ldGVzIiwibW91bnRfcGF0aCI6InRlc3QiLCJjb25maWciOnsicm9sZSI6InJvbGUiLCJ0b2tlbl9wYXRoIjoic2VydmljZWFjY291bnQvc29tZXdoZXJlL3Rva2VuIn19LCJzaW5rIjpbeyJ0eXBlIjoiZmlsZSIsImNvbmZpZyI6eyJwYXRoIjoiL2hvbWUvdmF1bHQvLnZhdWx0LXRva2VuIn19XX0sImV4aXRfYWZ0ZXJfYXV0aCI6ZmFsc2UsInBpZF9maWxlIjoiL2hvbWUvdmF1bHQvLnBpZCIsInZhdWx0Ijp7ImFkZHJlc3MiOiJodHRwOi8vZm9vYmFyOjEyMzQifSwidGVtcGxhdGVfY29uZmlnIjp7ImV4aXRfb25fcmV0cnlfZmFpbHVyZSI6dHJ1ZX19"}, + corev1.EnvVar{Name: "VAULT_CONFIG", Value: "eyJhdXRvX2F1dGgiOnsibWV0aG9kIjp7InR5cGUiOiJrdWJlcm5ldGVzIiwibW91bnRfcGF0aCI6InRlc3QiLCJjb25maWciOnsicm9sZSI6InJvbGUiLCJ0b2tlbl9wYXRoIjoia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zb21ld2hlcmUvdG9rZW4ifX0sInNpbmsiOlt7InR5cGUiOiJmaWxlIiwiY29uZmlnIjp7InBhdGgiOiIvaG9tZS92YXVsdC8udmF1bHQtdG9rZW4ifX1dfSwiZXhpdF9hZnRlcl9hdXRoIjpmYWxzZSwicGlkX2ZpbGUiOiIvaG9tZS92YXVsdC8ucGlkIiwidmF1bHQiOnsiYWRkcmVzcyI6Imh0dHA6Ly9mb29iYXI6MTIzNCJ9LCJ0ZW1wbGF0ZV9jb25maWciOnsiZXhpdF9vbl9yZXRyeV9mYWlsdXJlIjp0cnVlfX0="}, ), Resources: v1.ResourceRequirements{ Limits: v1.ResourceList{"cpu": resource.MustParse("500m"), "memory": resource.MustParse("128Mi")}, Requests: v1.ResourceList{"cpu": resource.MustParse("250m"), "memory": resource.MustParse("64Mi")}, }, VolumeMounts: []v1.VolumeMount{ - {Name: "foobar", ReadOnly: true, MountPath: "serviceaccount/somewhere"}, + {Name: "foobar", ReadOnly: true, MountPath: "kubernetes.io/serviceaccount/somewhere"}, {Name: "home-sidecar", MountPath: "/home/vault"}, {Name: "vault-secrets", MountPath: "/vault/secrets"}, }, @@ -1325,11 +1325,11 @@ func TestAgentJsonPatch(t *testing.T) { baseContainerEnvVars, corev1.EnvVar{Name: "VAULT_LOG_LEVEL", Value: "info"}, corev1.EnvVar{Name: "VAULT_LOG_FORMAT", Value: "standard"}, - corev1.EnvVar{Name: "VAULT_CONFIG", Value: "eyJhdXRvX2F1dGgiOnsibWV0aG9kIjp7InR5cGUiOiJrdWJlcm5ldGVzIiwibW91bnRfcGF0aCI6InRlc3QiLCJjb25maWciOnsicm9sZSI6InJvbGUiLCJ0b2tlbl9wYXRoIjoic2VydmljZWFjY291bnQvc29tZXdoZXJlL3Rva2VuIn19LCJzaW5rIjpbeyJ0eXBlIjoiZmlsZSIsImNvbmZpZyI6eyJwYXRoIjoiL2hvbWUvdmF1bHQvLnZhdWx0LXRva2VuIn19XX0sImV4aXRfYWZ0ZXJfYXV0aCI6dHJ1ZSwicGlkX2ZpbGUiOiIvaG9tZS92YXVsdC8ucGlkIiwidmF1bHQiOnsiYWRkcmVzcyI6Imh0dHA6Ly9mb29iYXI6MTIzNCJ9LCJ0ZW1wbGF0ZV9jb25maWciOnsiZXhpdF9vbl9yZXRyeV9mYWlsdXJlIjp0cnVlfX0="}, + corev1.EnvVar{Name: "VAULT_CONFIG", Value: "eyJhdXRvX2F1dGgiOnsibWV0aG9kIjp7InR5cGUiOiJrdWJlcm5ldGVzIiwibW91bnRfcGF0aCI6InRlc3QiLCJjb25maWciOnsicm9sZSI6InJvbGUiLCJ0b2tlbl9wYXRoIjoia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zb21ld2hlcmUvdG9rZW4ifX0sInNpbmsiOlt7InR5cGUiOiJmaWxlIiwiY29uZmlnIjp7InBhdGgiOiIvaG9tZS92YXVsdC8udmF1bHQtdG9rZW4ifX1dfSwiZXhpdF9hZnRlcl9hdXRoIjp0cnVlLCJwaWRfZmlsZSI6Ii9ob21lL3ZhdWx0Ly5waWQiLCJ2YXVsdCI6eyJhZGRyZXNzIjoiaHR0cDovL2Zvb2JhcjoxMjM0In0sInRlbXBsYXRlX2NvbmZpZyI6eyJleGl0X29uX3JldHJ5X2ZhaWx1cmUiOnRydWV9fQ=="}, ) baseInitContainer.VolumeMounts = []v1.VolumeMount{ {Name: "home-init", MountPath: "/home/vault"}, - {Name: "foobar", ReadOnly: true, MountPath: "serviceaccount/somewhere"}, + {Name: "foobar", ReadOnly: true, MountPath: "kubernetes.io/serviceaccount/somewhere"}, {Name: "vault-secrets", MountPath: "/vault/secrets"}, } baseInitContainer.Lifecycle = nil diff --git a/agent-inject/handler.go b/agent-inject/handler.go index d3588504..5588097d 100644 --- a/agent-inject/handler.go +++ b/agent-inject/handler.go @@ -52,6 +52,7 @@ type Handler struct { VaultCACertBytes string VaultAuthType string VaultAuthPath string + VaultAuthConfigExtraArgs map[string]string VaultNamespace string ProxyAddress string ImageVault string @@ -192,6 +193,7 @@ func (h *Handler) Mutate(req *admissionv1.AdmissionRequest) *admissionv1.Admissi Address: h.VaultAddress, AuthType: h.VaultAuthType, AuthPath: h.VaultAuthPath, + AuthConfigExtraArgs: h.VaultAuthConfigExtraArgs, VaultNamespace: h.VaultNamespace, ProxyAddress: h.ProxyAddress, Namespace: req.Namespace, diff --git a/agent-inject/handler_test.go b/agent-inject/handler_test.go index 53fb7cc1..036ec73d 100644 --- a/agent-inject/handler_test.go +++ b/agent-inject/handler_test.go @@ -38,7 +38,7 @@ func TestHandlerHandle(t *testing.T) { VolumeMounts: []corev1.VolumeMount{ { Name: "foobar", - MountPath: "serviceaccount/somewhere", + MountPath: "kubernetes.io/serviceaccount/somewhere", }, }, }, @@ -49,7 +49,7 @@ func TestHandlerHandle(t *testing.T) { VolumeMounts: []corev1.VolumeMount{ { Name: "foobar", - MountPath: "serviceaccount/somewhere", + MountPath: "kubernetes.io/serviceaccount/somewhere", }, }, }, diff --git a/subcommand/injector/command.go b/subcommand/injector/command.go index 1529b618..2562fb9b 100644 --- a/subcommand/injector/command.go +++ b/subcommand/injector/command.go @@ -59,6 +59,7 @@ type Command struct { flagVaultImage string // Name of the Vault Image to use flagVaultAuthType string // Type of Vault Auth Method to use flagVaultAuthPath string // Mount path of the Vault Auth Method + flagVaultAuthConfigExtraArgs string // Extra arguments for the Vault Auth Method Config block flagVaultNamespace string // Vault enterprise namespace flagRevokeOnShutdown bool // Revoke Vault Token on pod shutdown flagRunAsUser string // User (uid) to run Vault agent as @@ -192,11 +193,17 @@ func (c *Command) Run(args []string) int { go c.certWatcher(ctx, certCh, clientset, leaderElector, logger.Named("certwatcher")) // Build the HTTP handler and server + authConfigExtraArgs, err := c.makeVaultAuthConfigExtraArgs() + if err != nil { + c.UI.Error(fmt.Sprintf("Error reading VaultAuthConfigExtraArgs: %s", err)) + return 1 + } injector := agentInject.Handler{ VaultAddress: c.flagVaultService, VaultCACertBytes: c.flagVaultCACertBytes, VaultAuthType: c.flagVaultAuthType, VaultAuthPath: c.flagVaultAuthPath, + VaultAuthConfigExtraArgs: authConfigExtraArgs, VaultNamespace: c.flagVaultNamespace, ProxyAddress: c.flagProxyAddress, ImageVault: c.flagVaultImage, @@ -310,6 +317,22 @@ func (c *Command) handleReady(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(204) } +func (c *Command) makeVaultAuthConfigExtraArgs() (map[string]string, error) { + extraArgs := make(map[string]string) + if c.flagVaultAuthConfigExtraArgs == "" { + return extraArgs, nil + } + for _, extraArg := range strings.Split(c.flagVaultAuthConfigExtraArgs, ",") { + extraArgKey, extraArgVal, found := strings.Cut(extraArg, ":") + if !found { + return nil, fmt.Errorf("invalid auth config extra args: %s", + c.flagVaultAuthConfigExtraArgs) + } + extraArgs[strings.TrimSpace(extraArgKey)] = strings.TrimSpace(extraArgVal) + } + return extraArgs, nil +} + func (c *Command) makeTLSConfig() (*tls.Config, error) { minTLSVersion, ok := tlsutil.TLSLookup[c.flagTLSMinVersion] if !ok { diff --git a/subcommand/injector/flags.go b/subcommand/injector/flags.go index 5b809f50..26689ce6 100644 --- a/subcommand/injector/flags.go +++ b/subcommand/injector/flags.go @@ -78,6 +78,9 @@ type Specification struct { // VaultAuthPath is the AGENT_INJECT_VAULT_AUTH_PATH environment variable. VaultAuthPath string `split_words:"true"` + // VaultAuthConfigExtraArgs is the AGENT_INJECT_VAULT_AUTH_CONFIG_EXTRA_ARGS environment variable. + VaultAuthConfigExtraArgs string `split_words:"true"` + // VaultNamespace is the AGENT_INJECT_VAULT_NAMESPACE environment variable. VaultNamespace string `split_words:"true"` @@ -320,6 +323,10 @@ func (c *Command) parseEnvs() error { c.flagVaultAuthPath = envs.VaultAuthPath } + if envs.VaultAuthConfigExtraArgs != "" { + c.flagVaultAuthConfigExtraArgs = envs.VaultAuthConfigExtraArgs + } + if envs.VaultNamespace != "" { c.flagVaultNamespace = envs.VaultNamespace } diff --git a/subcommand/injector/flags_test.go b/subcommand/injector/flags_test.go index 854d9bb1..3e945f59 100644 --- a/subcommand/injector/flags_test.go +++ b/subcommand/injector/flags_test.go @@ -119,6 +119,7 @@ func TestCommandEnvs(t *testing.T) { {env: "AGENT_INJECT_VAULT_CACERT_BYTES", value: "foo", cmdPtr: &cmd.flagVaultCACertBytes}, {env: "AGENT_INJECT_PROXY_ADDR", value: "http://proxy:3128", cmdPtr: &cmd.flagProxyAddress}, {env: "AGENT_INJECT_VAULT_AUTH_PATH", value: "auth-path-test", cmdPtr: &cmd.flagVaultAuthPath}, + {env: "AGENT_INJECT_VAULT_AUTH_CONFIG_EXTRA_ARGS", value: "key1:val1", cmdPtr: &cmd.flagVaultAuthConfigExtraArgs}, {env: "AGENT_INJECT_VAULT_IMAGE", value: "hashicorp/vault:1.15.1", cmdPtr: &cmd.flagVaultImage}, {env: "AGENT_INJECT_VAULT_NAMESPACE", value: "test-namespace", cmdPtr: &cmd.flagVaultNamespace}, {env: "AGENT_INJECT_TLS_KEY_FILE", value: "server.key", cmdPtr: &cmd.flagKeyFile},