From b5d27ae7497c3077b4d0c8cc1f79642959f2d7a8 Mon Sep 17 00:00:00 2001 From: Pierce Bartine Date: Fri, 15 Jan 2021 17:37:18 -0800 Subject: [PATCH] Generalize Agent Auto Auth to allow all methods No fail if Role not specified unless K8s auth Add tests Signed-off-by: Pierce Bartine --- agent-inject/agent/agent.go | 13 ++- agent-inject/agent/annotations.go | 34 +++++++- agent-inject/agent/annotations_test.go | 86 +++++++++++++++++--- agent-inject/agent/config.go | 6 +- agent-inject/agent/config_test.go | 8 +- agent-inject/agent/container_sidecar_test.go | 8 +- agent-inject/handler.go | 2 + subcommand/injector/command.go | 4 +- subcommand/injector/flags.go | 11 ++- 9 files changed, 142 insertions(+), 30 deletions(-) diff --git a/agent-inject/agent/agent.go b/agent-inject/agent/agent.go index c691b8a8..15634e33 100644 --- a/agent-inject/agent/agent.go +++ b/agent-inject/agent/agent.go @@ -15,6 +15,7 @@ import ( const ( DefaultVaultImage = "vault:1.6.1" + DefaultVaultAuthType = "kubernetes" DefaultVaultAuthPath = "auth/kubernetes" DefaultAgentRunAsUser = 100 DefaultAgentRunAsGroup = 1000 @@ -156,9 +157,15 @@ type Vault struct { // Address is the Vault service address. Address string - // AuthPath is the Mount Path of Vault Kubernetes Auth Method. + // AuthType is type of Vault Auth Method to use. + AuthType string + + // AuthPath is the Mount Path of Vault Auth Method. AuthPath string + // AuthConfig is the Auto Auth Method configuration. + AuthConfig map[string]interface{} + // CACert is the name of the Certificate Authority certificate // to use when validating Vault's server certificates. CACert string @@ -236,6 +243,7 @@ func New(pod *corev1.Pod, patches []*jsonpatch.JsonPatchOperation) (*Agent, erro ExtraSecret: pod.Annotations[AnnotationAgentExtraSecret], Vault: Vault{ Address: pod.Annotations[AnnotationVaultService], + AuthType: pod.Annotations[AnnotationVaultAuthType], AuthPath: pod.Annotations[AnnotationVaultAuthPath], CACert: pod.Annotations[AnnotationVaultCACert], CAKey: pod.Annotations[AnnotationVaultCAKey], @@ -253,6 +261,7 @@ func New(pod *corev1.Pod, patches []*jsonpatch.JsonPatchOperation) (*Agent, erro var err error agent.Secrets = agent.secrets() + agent.Vault.AuthConfig = agent.authConfig() agent.Inject, err = agent.inject() if err != nil { return agent, err @@ -495,7 +504,7 @@ func (a *Agent) Validate() error { } if a.ConfigMapName == "" { - if a.Vault.Role == "" { + if a.Vault.Role == "" && a.Vault.AuthType == DefaultVaultAuthType { return errors.New("no Vault role found") } diff --git a/agent-inject/agent/annotations.go b/agent-inject/agent/annotations.go index d510e3fc..41c6b859 100644 --- a/agent-inject/agent/annotations.go +++ b/agent-inject/agent/annotations.go @@ -169,10 +169,17 @@ const ( // method. AnnotationVaultRole = "vault.hashicorp.com/role" - // AnnotationVaultAuthPath specifies the mount path to be used for the Kubernetes auto-auth - // method. + // AnnotationVaultAuthType specifies the auto-auth method type to be used. + AnnotationVaultAuthType = "vault.hashicorp.com/auth-type" + + // AnnotationVaultAuthPath specifies the mount path to be used for the auto-auth method. AnnotationVaultAuthPath = "vault.hashicorp.com/auth-path" + // AnnotationVaultAuthConfig specifies the Auto Auth Method configuration parameters. + // The name of the parameter is any unique string after "vault.hashicorp.com/auth-config-", + // such as "vault.hashicorp.com/auth-config-foobar". + AnnotationVaultAuthConfig = "vault.hashicorp.com/auth-config" + // AnnotationVaultSecretVolumePath specifies where the secrets are to be // Mounted after fetching. AnnotationVaultSecretVolumePath = "vault.hashicorp.com/secret-volume-path" @@ -196,6 +203,7 @@ const ( type AgentConfig struct { Image string Address string + AuthType string AuthPath string Namespace string RevokeOnShutdown bool @@ -238,6 +246,10 @@ func Init(pod *corev1.Pod, cfg AgentConfig) error { pod.ObjectMeta.Annotations[AnnotationVaultService] = cfg.Address } + if _, ok := pod.ObjectMeta.Annotations[AnnotationVaultAuthType]; !ok { + pod.ObjectMeta.Annotations[AnnotationVaultAuthType] = cfg.AuthType + } + if _, ok := pod.ObjectMeta.Annotations[AnnotationVaultAuthPath]; !ok { pod.ObjectMeta.Annotations[AnnotationVaultAuthPath] = cfg.AuthPath } @@ -514,3 +526,21 @@ func (a *Agent) agentCacheEnable() (bool, error) { return strconv.ParseBool(raw) } + +func (a *Agent) authConfig() map[string]interface{} { + authConfig := make(map[string]interface{}) + + prefix := fmt.Sprintf("%s-", AnnotationVaultAuthConfig) + for annotation, value := range a.Annotations { + if strings.HasPrefix(annotation, prefix) { + param := strings.TrimPrefix(annotation, prefix) + param = strings.ReplaceAll(param, "-", "_") + authConfig[param] = value + } + } + if len(authConfig) == 0 && a.Vault.AuthType == DefaultVaultAuthType { + authConfig["role"] = a.Vault.Role + } + + return authConfig +} diff --git a/agent-inject/agent/annotations_test.go b/agent-inject/agent/annotations_test.go index 94857533..0a3c0e40 100644 --- a/agent-inject/agent/annotations_test.go +++ b/agent-inject/agent/annotations_test.go @@ -19,7 +19,7 @@ func TestInitCanSet(t *testing.T) { pod := testPod(annotations) agentConfig := AgentConfig{ - "foobar-image", "http://foobar:8200", "test", "test", true, "100", "1000", + "foobar-image", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) @@ -55,7 +55,7 @@ func TestInitDefaults(t *testing.T) { pod := testPod(annotations) agentConfig := AgentConfig{ - "", "http://foobar:8200", "test", "test", true, "", "", + "", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "", "", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) @@ -89,7 +89,7 @@ func TestInitError(t *testing.T) { pod := testPod(annotations) agentConfig := AgentConfig{ - "image", "", "authPath", "namespace", true, "100", "1000", + "image", "", DefaultVaultAuthType, "authPath", "namespace", true, "100", "1000", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) @@ -153,7 +153,7 @@ func TestSecretAnnotationsWithPreserveCaseSensitivityFlagOff(t *testing.T) { var patches []*jsonpatch.JsonPatchOperation agentConfig := AgentConfig{ - "", "http://foobar:8200", "test", "test", true, "100", "1000", + "", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) @@ -204,7 +204,7 @@ func TestSecretAnnotationsWithPreserveCaseSensitivityFlagOn(t *testing.T) { var patches []*jsonpatch.JsonPatchOperation agentConfig := AgentConfig{ - "", "http://foobar:8200", "test", "test", true, "100", "1000", + "", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) @@ -291,7 +291,7 @@ func TestSecretLocationFileAnnotations(t *testing.T) { var patches []*jsonpatch.JsonPatchOperation agentConfig := AgentConfig{ - "", "http://foobar:8200", "test", "test", true, "100", "1000", + "", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) @@ -376,7 +376,7 @@ func TestSecretTemplateAnnotations(t *testing.T) { var patches []*jsonpatch.JsonPatchOperation agentConfig := AgentConfig{ - "", "http://foobar:8200", "test", "test", true, "100", "1000", + "", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) @@ -436,7 +436,7 @@ func TestTemplateShortcuts(t *testing.T) { t.Run(tt.name, func(t *testing.T) { pod := testPod(tt.annotations) agentConfig := AgentConfig{ - "", "http://foobar:8200", "test", "test", true, "100", "1000", + "", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) @@ -496,7 +496,7 @@ func TestSecretCommandAnnotations(t *testing.T) { for _, tt := range tests { pod := testPod(tt.annotations) agentConfig := AgentConfig{ - "", "http://foobar:8200", "test", "test", true, "100", "1000", + "", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) @@ -629,7 +629,7 @@ func TestCouldErrorAnnotations(t *testing.T) { var patches []*jsonpatch.JsonPatchOperation agentConfig := AgentConfig{ - "", "http://foobar:8200", "test", "test", true, "100", "1000", + "", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) @@ -650,7 +650,7 @@ func TestInitEmptyPod(t *testing.T) { var pod *corev1.Pod agentConfig := AgentConfig{ - "foobar-image", "http://foobar:8200", "test", "test", true, "100", "1000", + "foobar-image", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) @@ -679,7 +679,7 @@ func TestVaultNamespaceAnnotation(t *testing.T) { var patches []*jsonpatch.JsonPatchOperation agentConfig := AgentConfig{ - "foobar-image", "http://foobar:8200", "test", "test", true, "100", "1000", + "foobar-image", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) @@ -793,3 +793,65 @@ func Test_runAsSameID(t *testing.T) { }) } } + +func TestAuthConfigAnnotations(t *testing.T) { + tests := []struct { + annotations map[string]string + expectedAuthConfig map[string]interface{} + }{ + { + map[string]string{ + "vault.hashicorp.com/role": "backwardscompat", + }, + map[string]interface{}{ + "role": "backwardscompat", + }, + }, + { + map[string]string{ + "vault.hashicorp.com/role": "backwardscompat", + "vault.hashicorp.com/auth-config-role": "takesprecedence", + }, + map[string]interface{}{ + "role": "takesprecedence", + }, + }, + { + map[string]string{ + "vault.hashicorp.com/auth-config-name": "foo", + "vault.hashicorp.com/auth-config-ca-cert": "bar", + "vault.hashicorp.com/auth-config-client_cert": "baz", + "vault.hashicorp.com/auth-config-credential_poll_interval": "1", + "vault.hashicorp.com/auth-config-remove_secret_id_file_after_reading": "false", + }, + map[string]interface{}{ + "name": "foo", + "ca_cert": "bar", // param name dashes converted to underscores for ease + "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 + }, + }, + } + + for _, tt := range tests { + pod := testPod(tt.annotations) + var patches []*jsonpatch.JsonPatchOperation + + agentConfig := AgentConfig{ + "", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000", + DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, + } + err := Init(pod, agentConfig) + if err != nil { + t.Errorf("got error, shouldn't have: %s", err) + } + + agent, err := New(pod, patches) + if err != nil { + t.Errorf("got error, shouldn't have: %s", err) + } + + require.Equal(t, agent.Vault.AuthConfig, tt.expectedAuthConfig, "expected AuthConfig %v, got %v", tt.expectedAuthConfig, agent.Vault.AuthConfig) + } +} diff --git a/agent-inject/agent/config.go b/agent-inject/agent/config.go index c503954e..540af1a7 100644 --- a/agent-inject/agent/config.go +++ b/agent-inject/agent/config.go @@ -128,12 +128,10 @@ func (a *Agent) newConfig(init bool) ([]byte, error) { }, AutoAuth: &AutoAuth{ Method: &Method{ - Type: "kubernetes", + Type: a.Vault.AuthType, Namespace: a.Vault.Namespace, MountPath: a.Vault.AuthPath, - Config: map[string]interface{}{ - "role": a.Vault.Role, - }, + Config: a.Vault.AuthConfig, }, Sinks: []*Sink{ { diff --git a/agent-inject/agent/config_test.go b/agent-inject/agent/config_test.go index 3a9a332f..77698ef3 100644 --- a/agent-inject/agent/config_test.go +++ b/agent-inject/agent/config_test.go @@ -41,7 +41,7 @@ func TestNewConfig(t *testing.T) { var patches []*jsonpatch.JsonPatchOperation agentConfig := AgentConfig{ - "foobar-image", "http://foobar:8200", "test", "test", true, "100", "1000", + "foobar-image", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) @@ -207,7 +207,7 @@ func TestFilePathAndName(t *testing.T) { var patches []*jsonpatch.JsonPatchOperation agentConfig := AgentConfig{ - "foobar-image", "http://foobar:8200", "test", "test", true, "100", "1000", + "foobar-image", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) @@ -239,7 +239,7 @@ func TestConfigVaultAgentCacheNotEnabledByDefault(t *testing.T) { var patches []*jsonpatch.JsonPatchOperation agentConfig := AgentConfig{ - "foobar-image", "http://foobar:8200", "test", "test", true, "100", "1000", + "foobar-image", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) @@ -278,7 +278,7 @@ func TestConfigVaultAgentCache(t *testing.T) { var patches []*jsonpatch.JsonPatchOperation agentConfig := AgentConfig{ - "foobar-image", "http://foobar:8200", "test", "test", true, "100", "1000", + "foobar-image", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, } err := Init(pod, agentConfig) diff --git a/agent-inject/agent/container_sidecar_test.go b/agent-inject/agent/container_sidecar_test.go index 7e700ae2..e39aea8a 100644 --- a/agent-inject/agent/container_sidecar_test.go +++ b/agent-inject/agent/container_sidecar_test.go @@ -36,7 +36,7 @@ func TestContainerSidecarVolume(t *testing.T) { pod := testPod(annotations) var patches []*jsonpatch.JsonPatchOperation - err := Init(pod, AgentConfig{"foobar-image", "http://foobar:1234", "test", "test", true, "1000", "100", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext}) + err := Init(pod, AgentConfig{"foobar-image", "http://foobar:1234", DefaultVaultAuthType, "test", "test", true, "1000", "100", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext}) if err != nil { t.Errorf("got error, shouldn't have: %s", err) } @@ -92,7 +92,7 @@ func TestContainerSidecar(t *testing.T) { pod := testPod(annotations) var patches []*jsonpatch.JsonPatchOperation - err := Init(pod, AgentConfig{"foobar-image", "http://foobar:1234", "test", "test", false, "1000", "100", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext}) + err := Init(pod, AgentConfig{"foobar-image", "http://foobar:1234", DefaultVaultAuthType, "test", "test", false, "1000", "100", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext}) if err != nil { t.Errorf("got error, shouldn't have: %s", err) } @@ -193,7 +193,7 @@ func TestContainerSidecarRevokeHook(t *testing.T) { pod := testPod(annotations) var patches []*jsonpatch.JsonPatchOperation - err := Init(pod, AgentConfig{"foobar-image", "http://foobar:1234", "test", "test", tt.revokeFlag, "1000", "100", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext}) + err := Init(pod, AgentConfig{"foobar-image", "http://foobar:1234", DefaultVaultAuthType, "test", "test", tt.revokeFlag, "1000", "100", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext}) if err != nil { t.Errorf("got error, shouldn't have: %s", err) } @@ -242,7 +242,7 @@ func TestContainerSidecarConfigMap(t *testing.T) { pod := testPod(annotations) var patches []*jsonpatch.JsonPatchOperation - err := Init(pod, AgentConfig{"foobar-image", "http://foobar:1234", "test", "test", true, "1000", "100", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext}) + err := Init(pod, AgentConfig{"foobar-image", "http://foobar:1234", DefaultVaultAuthType, "test", "test", true, "1000", "100", DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext}) if err != nil { t.Errorf("got error, shouldn't have: %s", err) } diff --git a/agent-inject/handler.go b/agent-inject/handler.go index 260ec21c..72fc6666 100644 --- a/agent-inject/handler.go +++ b/agent-inject/handler.go @@ -37,6 +37,7 @@ type Handler struct { // If this is false, injection is default. RequireAnnotation bool VaultAddress string + VaultAuthType string VaultAuthPath string ImageVault string Clientset *kubernetes.Clientset @@ -143,6 +144,7 @@ func (h *Handler) Mutate(req *v1beta1.AdmissionRequest) *v1beta1.AdmissionRespon cfg := agent.AgentConfig{ Image: h.ImageVault, Address: h.VaultAddress, + AuthType: h.VaultAuthType, AuthPath: h.VaultAuthPath, Namespace: req.Namespace, RevokeOnShutdown: h.RevokeOnShutdown, diff --git a/subcommand/injector/command.go b/subcommand/injector/command.go index 94a81c69..d020afac 100644 --- a/subcommand/injector/command.go +++ b/subcommand/injector/command.go @@ -41,7 +41,8 @@ type Command struct { flagAutoHosts string // SANs for the auto-generated TLS cert. flagVaultService string // Name of the Vault service flagVaultImage string // Name of the Vault Image to use - flagVaultAuthPath string // Mount Path of the Vault Kubernetes Auth Method + flagVaultAuthType string // Type of Vault Auth Method to use + flagVaultAuthPath string // Mount path of the Vault Auth Method flagRevokeOnShutdown bool // Revoke Vault Token on pod shutdown flagRunAsUser string // User (uid) to run Vault agent as flagRunAsGroup string // Group (gid) to run Vault agent as @@ -143,6 +144,7 @@ func (c *Command) Run(args []string) int { // Build the HTTP handler and server injector := agentInject.Handler{ VaultAddress: c.flagVaultService, + VaultAuthType: c.flagVaultAuthType, VaultAuthPath: c.flagVaultAuthPath, ImageVault: c.flagVaultImage, Clientset: clientset, diff --git a/subcommand/injector/flags.go b/subcommand/injector/flags.go index f6eb22f2..27200afa 100644 --- a/subcommand/injector/flags.go +++ b/subcommand/injector/flags.go @@ -48,6 +48,9 @@ type Specification struct { // VaultImage is the AGENT_INJECT_VAULT_IMAGE environment variable. VaultImage string `split_words:"true"` + // VaultAuthType is the AGENT_INJECT_VAULT_AUTH_TYPE environment variable. + VaultAuthType string `split_words:"true"` + // VaultAuthPath is the AGENT_INJECT_VAULT_AUTH_PATH environment variable. VaultAuthPath string `split_words:"true"` @@ -92,8 +95,10 @@ func (c *Command) init() { fmt.Sprintf("Docker image for Vault. Defaults to %q.", agent.DefaultVaultImage)) c.flagSet.StringVar(&c.flagVaultService, "vault-address", "", "Address of the Vault server.") + c.flagSet.StringVar(&c.flagVaultAuthType, "vault-auth-type", agent.DefaultVaultAuthType, + fmt.Sprintf("Type of Vault Auth Method to use. Defaults to %q.", agent.DefaultVaultAuthType)) c.flagSet.StringVar(&c.flagVaultAuthPath, "vault-auth-path", agent.DefaultVaultAuthPath, - fmt.Sprintf("Mount Path of the Vault Kubernetes Auth Method. Defaults to %q.", agent.DefaultVaultAuthPath)) + fmt.Sprintf("Mount path of the Vault Auth Method. Defaults to %q.", agent.DefaultVaultAuthPath)) c.flagSet.BoolVar(&c.flagRevokeOnShutdown, "revoke-on-shutdown", false, "Automatically revoke Vault Token on Pod termination.") c.flagSet.StringVar(&c.flagRunAsUser, "run-as-user", strconv.Itoa(agent.DefaultAgentRunAsUser), @@ -179,6 +184,10 @@ func (c *Command) parseEnvs() error { c.flagVaultService = envs.VaultAddr } + if envs.VaultAuthType != "" { + c.flagVaultAuthType = envs.VaultAuthType + } + if envs.VaultAuthPath != "" { c.flagVaultAuthPath = envs.VaultAuthPath }