From f30434e22cf70f55f991abad90ab216dcd699e81 Mon Sep 17 00:00:00 2001 From: joe miller Date: Tue, 25 Feb 2020 17:41:17 -0800 Subject: [PATCH] set run-as-{user,group} globally in the injector via environment variables --- agent-inject/agent/annotations.go | 42 ++++++++++++++------ agent-inject/agent/annotations_test.go | 30 ++++++++------ agent-inject/agent/container_sidecar_test.go | 8 ++-- agent-inject/handler.go | 13 +++++- subcommand/injector/command.go | 4 ++ subcommand/injector/flags.go | 20 +++++++++- subcommand/injector/flags_test.go | 2 + 7 files changed, 87 insertions(+), 32 deletions(-) diff --git a/agent-inject/agent/annotations.go b/agent-inject/agent/annotations.go index 913fb28d..a213ddb9 100644 --- a/agent-inject/agent/annotations.go +++ b/agent-inject/agent/annotations.go @@ -146,23 +146,33 @@ const ( AnnotationVaultAuthPath = "vault.hashicorp.com/auth-path" ) +type AgentConfig struct { + Image string + Address string + AuthPath string + Namespace string + RevokeOnShutdown bool + UserID string + GroupID string +} + // Init configures the expected annotations required to create a new instance // of Agent. This should be run before running new to ensure all annotations are // present. -func Init(pod *corev1.Pod, image, address, authPath, namespace string, revokeOnShutdown bool) error { +func Init(pod *corev1.Pod, cfg AgentConfig) error { if pod == nil { return errors.New("pod is empty") } - if address == "" { + if cfg.Address == "" { return errors.New("address for Vault required") } - if authPath == "" { + if cfg.AuthPath == "" { return errors.New("Vault Auth Path required") } - if namespace == "" { + if cfg.Namespace == "" { return errors.New("kubernetes namespace required") } @@ -171,22 +181,22 @@ func Init(pod *corev1.Pod, image, address, authPath, namespace string, revokeOnS } if _, ok := pod.ObjectMeta.Annotations[AnnotationVaultService]; !ok { - pod.ObjectMeta.Annotations[AnnotationVaultService] = address + pod.ObjectMeta.Annotations[AnnotationVaultService] = cfg.Address } if _, ok := pod.ObjectMeta.Annotations[AnnotationVaultAuthPath]; !ok { - pod.ObjectMeta.Annotations[AnnotationVaultAuthPath] = authPath + pod.ObjectMeta.Annotations[AnnotationVaultAuthPath] = cfg.AuthPath } if _, ok := pod.ObjectMeta.Annotations[AnnotationAgentImage]; !ok { - if image == "" { - image = DefaultVaultImage + if cfg.Image == "" { + cfg.Image = DefaultVaultImage } - pod.ObjectMeta.Annotations[AnnotationAgentImage] = image + pod.ObjectMeta.Annotations[AnnotationAgentImage] = cfg.Image } if _, ok := pod.ObjectMeta.Annotations[AnnotationAgentRequestNamespace]; !ok { - pod.ObjectMeta.Annotations[AnnotationAgentRequestNamespace] = namespace + pod.ObjectMeta.Annotations[AnnotationAgentRequestNamespace] = cfg.Namespace } if _, ok := pod.ObjectMeta.Annotations[AnnotationAgentLimitsCPU]; !ok { @@ -206,7 +216,7 @@ func Init(pod *corev1.Pod, image, address, authPath, namespace string, revokeOnS } if _, ok := pod.ObjectMeta.Annotations[AnnotationAgentRevokeOnShutdown]; !ok { - pod.ObjectMeta.Annotations[AnnotationAgentRevokeOnShutdown] = strconv.FormatBool(revokeOnShutdown) + pod.ObjectMeta.Annotations[AnnotationAgentRevokeOnShutdown] = strconv.FormatBool(cfg.RevokeOnShutdown) } if _, ok := pod.ObjectMeta.Annotations[AnnotationAgentRevokeGrace]; !ok { @@ -218,11 +228,17 @@ func Init(pod *corev1.Pod, image, address, authPath, namespace string, revokeOnS } if _, ok := pod.ObjectMeta.Annotations[AnnotationAgentRunAsUser]; !ok { - pod.ObjectMeta.Annotations[AnnotationAgentRunAsUser] = strconv.Itoa(DefaultAgentRunAsUser) + if cfg.UserID == "" { + cfg.UserID = strconv.Itoa(DefaultAgentRunAsUser) + } + pod.ObjectMeta.Annotations[AnnotationAgentRunAsUser] = cfg.UserID } if _, ok := pod.ObjectMeta.Annotations[AnnotationAgentRunAsGroup]; !ok { - pod.ObjectMeta.Annotations[AnnotationAgentRunAsGroup] = strconv.Itoa(DefaultAgentRunAsGroup) + if cfg.GroupID == "" { + cfg.GroupID = strconv.Itoa(DefaultAgentRunAsGroup) + } + pod.ObjectMeta.Annotations[AnnotationAgentRunAsGroup] = cfg.GroupID } return nil diff --git a/agent-inject/agent/annotations_test.go b/agent-inject/agent/annotations_test.go index 720d266c..47aba2bd 100644 --- a/agent-inject/agent/annotations_test.go +++ b/agent-inject/agent/annotations_test.go @@ -15,7 +15,7 @@ func TestInitCanSet(t *testing.T) { annotations := make(map[string]string) pod := testPod(annotations) - err := Init(pod, "foobar-image", "http://foobar:8200", "test", "test", true) + err := Init(pod, AgentConfig{"foobar-image", "http://foobar:8200", "test", "test", true, "1000", "100"}) if err != nil { t.Errorf("got error, shouldn't have: %s", err) } @@ -47,7 +47,7 @@ func TestInitDefaults(t *testing.T) { annotations := make(map[string]string) pod := testPod(annotations) - err := Init(pod, "", "http://foobar:8200", "test", "test", true) + err := Init(pod, AgentConfig{"", "http://foobar:8200", "test", "test", true, "", ""}) if err != nil { t.Errorf("got error, shouldn't have: %s", err) } @@ -69,7 +69,6 @@ func TestInitDefaults(t *testing.T) { if raw != tt.annotationValue { t.Errorf("Default annotation value incorrect, wanted %s, got %s", tt.annotationValue, raw) - } } } @@ -78,7 +77,7 @@ func TestInitError(t *testing.T) { annotations := make(map[string]string) pod := testPod(annotations) - err := Init(pod, "image", "", "authPath", "namespace", true) + err := Init(pod, AgentConfig{"image", "", "authPath", "namespace", true, "1000", "100"}) if err == nil { t.Error("expected error no address, got none") } @@ -88,7 +87,7 @@ func TestInitError(t *testing.T) { t.Errorf("expected '%s' error, got %s", errMsg, err) } - err = Init(pod, "image", "address", "", "namespace", true) + err = Init(pod, AgentConfig{"image", "address", "", "namespace", true, "1000", "100"}) if err == nil { t.Error("expected error no authPath, got none") } @@ -98,7 +97,7 @@ func TestInitError(t *testing.T) { t.Errorf("expected '%s' error, got %s", errMsg, err) } - err = Init(pod, "image", "address", "authPath", "", true) + err = Init(pod, AgentConfig{"image", "address", "authPath", "", true, "1000", "100"}) if err == nil { t.Error("expected error for no namespace, got none") } @@ -131,7 +130,7 @@ func TestSecretAnnotations(t *testing.T) { pod := testPod(annotation) var patches []*jsonpatch.JsonPatchOperation - err := Init(pod, "", "http://foobar:8200", "test", "test") + err := Init(pod, AgentConfig{"", "http://foobar:8200", "test", "test", true, "1000", "100"}) if err != nil { t.Errorf("got error, shouldn't have: %s", err) } @@ -209,7 +208,7 @@ func TestSecretTemplateAnnotations(t *testing.T) { pod := testPod(tt.annotations) var patches []*jsonpatch.JsonPatchOperation - err := Init(pod, "", "http://foobar:8200", "test", "test") + err := Init(pod, AgentConfig{"", "http://foobar:8200", "test", "test", true, "1000", "100"}) if err != nil { t.Errorf("got error, shouldn't have: %s", err) } @@ -264,7 +263,7 @@ func TestTemplateShortcuts(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pod := testPod(tt.annotations) - err := Init(pod, "", "http://foobar:8200", "test", "test") + err := Init(pod, AgentConfig{"", "http://foobar:8200", "test", "test", true, "1000", "100"}) if err != nil { t.Errorf("got error, shouldn't have: %s", err) } @@ -320,7 +319,7 @@ func TestSecretCommandAnnotations(t *testing.T) { for _, tt := range tests { pod := testPod(tt.annotations) - err := Init(pod, "", "http://foobar:8200", "test", "test") + err := Init(pod, AgentConfig{"", "http://foobar:8200", "test", "test", true, "1000", "100"}) if err != nil { t.Errorf("got error, shouldn't have: %s", err) } @@ -431,7 +430,7 @@ func TestCouldErrorAnnotations(t *testing.T) { pod := testPod(annotations) var patches []*jsonpatch.JsonPatchOperation - err := Init(pod, "", "http://foobar:8200", "test", "test") + err := Init(pod, AgentConfig{"", "http://foobar:8200", "test", "test", true, "1000", "100"}) if err != nil { t.Errorf("got error, shouldn't have: %s", err) } @@ -448,9 +447,9 @@ func TestCouldErrorAnnotations(t *testing.T) { func TestInitEmptyPod(t *testing.T) { var pod *corev1.Pod - err := Init(pod, "foobar-image", "http://foobar:8200", "test", "test", true) + err := Init(pod, AgentConfig{"foobar-image", "http://foobar:8200", "test", "test", true, "1000", "100"}) if err == nil { - t.Errorf("got no error, shouldn have") + t.Errorf("got no error, should have") } } @@ -473,6 +472,11 @@ func TestVaultNamespaceAnnotation(t *testing.T) { pod := testPod(annotation) var patches []*jsonpatch.JsonPatchOperation + err := Init(pod, AgentConfig{"foobar-image", "http://foobar:8200", "test", "test", true, "1000", "100"}) + 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) diff --git a/agent-inject/agent/container_sidecar_test.go b/agent-inject/agent/container_sidecar_test.go index 9f952ae4..e33a0fc9 100644 --- a/agent-inject/agent/container_sidecar_test.go +++ b/agent-inject/agent/container_sidecar_test.go @@ -16,7 +16,7 @@ func TestContainerSidecar(t *testing.T) { pod := testPod(annotations) var patches []*jsonpatch.JsonPatchOperation - err := Init(pod, "foobar-image", "http://foobar:1234", "test", "test", false) + err := Init(pod, AgentConfig{"foobar-image", "http://foobar:1234", "test", "test", false, "1000", "100"}) if err != nil { t.Errorf("got error, shouldn't have: %s", err) } @@ -111,7 +111,7 @@ func TestContainerSidecarRevokeHook(t *testing.T) { pod := testPod(annotations) var patches []*jsonpatch.JsonPatchOperation - err := Init(pod, "foobar-image", "http://foobar:1234", "test", "test", tt.revokeFlag) + err := Init(pod, AgentConfig{"foobar-image", "http://foobar:1234", "test", "test", tt.revokeFlag, "1000", "100"}) if err != nil { t.Errorf("got error, shouldn't have: %s", err) } @@ -159,7 +159,7 @@ func TestContainerSidecarConfigMap(t *testing.T) { pod := testPod(annotations) var patches []*jsonpatch.JsonPatchOperation - err := Init(pod, "foobar-image", "http://foobar:1234", "test", "test", true) + err := Init(pod, AgentConfig{"foobar-image", "http://foobar:1234", "test", "test", true, "1000", "100"}) if err != nil { t.Errorf("got error, shouldn't have: %s", err) } @@ -518,7 +518,7 @@ func TestContainerSidecarSecurityContext(t *testing.T) { pod := testPod(annotations) var patches []*jsonpatch.JsonPatchOperation - err := Init(pod, "foobar-image", "http://foobar:1234", "test", "test") + err := Init(pod, AgentConfig{"foobar-image", "http://foobar:1234", "test", "test", true, "1000", "100"}) 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 a8a6da0a..95ab0bfc 100644 --- a/agent-inject/handler.go +++ b/agent-inject/handler.go @@ -42,6 +42,8 @@ type Handler struct { Clientset *kubernetes.Clientset Log hclog.Logger RevokeOnShutdown bool + UserID string + GroupID string } // Handle is the http.HandlerFunc implementation that actually handles the @@ -136,7 +138,16 @@ func (h *Handler) Mutate(req *v1beta1.AdmissionRequest) *v1beta1.AdmissionRespon h.Log.Debug("setting default annotations..") var patches []*jsonpatch.JsonPatchOperation - err = agent.Init(&pod, h.ImageVault, h.VaultAddress, h.VaultAuthPath, req.Namespace, h.RevokeOnShutdown) + cfg := agent.AgentConfig{ + Image: h.ImageVault, + Address: h.VaultAddress, + AuthPath: h.VaultAuthPath, + Namespace: req.Namespace, + RevokeOnShutdown: h.RevokeOnShutdown, + UserID: h.UserID, + GroupID: h.GroupID, + } + err = agent.Init(&pod, cfg) if err != nil { err := fmt.Errorf("error adding default annotations: %s", err) return admissionError(err) diff --git a/subcommand/injector/command.go b/subcommand/injector/command.go index e8599b6d..714642df 100644 --- a/subcommand/injector/command.go +++ b/subcommand/injector/command.go @@ -38,6 +38,8 @@ type Command struct { flagVaultImage string // Name of the Vault Image to use flagVaultAuthPath string // Mount Path of the Vault Kubernetes 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 flagSet *flag.FlagSet @@ -118,6 +120,8 @@ func (c *Command) Run(args []string) int { RequireAnnotation: true, Log: logger, RevokeOnShutdown: c.flagRevokeOnShutdown, + UserID: c.flagRunAsUser, + GroupID: c.flagRunAsGroup, } mux := http.NewServeMux() diff --git a/subcommand/injector/flags.go b/subcommand/injector/flags.go index 3192d8c7..1ae289b9 100644 --- a/subcommand/injector/flags.go +++ b/subcommand/injector/flags.go @@ -52,7 +52,13 @@ type Specification struct { VaultAuthPath string `split_words:"true"` // RevokeOnShutdown is AGENT_INJECT_REVOKE_ON_SHUTDOWN environment variable. - RevokeOnShutdown string `split_words:"true" ` + RevokeOnShutdown string `split_words:"true"` + + // RunAsUser is the AGENT_INJECT_RUN_AS_USER environment variable. (uid) + RunAsUser string `envconfig:"AGENT_INJECT_RUN_AS_USER"` + + // RunAsGroup is the AGENT_INJECT_RUN_AS_GROUP environment variable. (gid) + RunAsGroup string `envconfig:"AGENT_INJECT_RUN_AS_GROUP"` } func (c *Command) init() { @@ -78,6 +84,10 @@ func (c *Command) init() { fmt.Sprintf("Mount Path of the Vault Kubernetes 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), + fmt.Sprintf("User (uid) to run Vault agent as. Defaults to %d.", agent.DefaultAgentRunAsUser)) + c.flagSet.StringVar(&c.flagRunAsGroup, "run-as-group", strconv.Itoa(agent.DefaultAgentRunAsGroup), + fmt.Sprintf("Group (gid) to run Vault agent as. Defaults to %d.", agent.DefaultAgentRunAsGroup)) c.help = flags.Usage(help, c.flagSet) } @@ -158,5 +168,13 @@ func (c *Command) parseEnvs() error { } } + if envs.RunAsUser != "" { + c.flagRunAsUser = envs.RunAsUser + } + + if envs.RunAsGroup != "" { + c.flagRunAsGroup = envs.RunAsGroup + } + return nil } diff --git a/subcommand/injector/flags_test.go b/subcommand/injector/flags_test.go index 699ad174..3fe8ceb0 100644 --- a/subcommand/injector/flags_test.go +++ b/subcommand/injector/flags_test.go @@ -121,6 +121,8 @@ func TestCommandEnvs(t *testing.T) { {env: "AGENT_INJECT_TLS_AUTO", value: "mutationWebhook", cmdPtr: &cmd.flagAutoName}, {env: "AGENT_INJECT_LOG_LEVEL", value: "info", cmdPtr: &cmd.flagLogLevel}, {env: "AGENT_INJECT_LOG_FORMAT", value: "standard", cmdPtr: &cmd.flagLogFormat}, + {env: "AGENT_INJECT_RUN_AS_USER", value: "1000", cmdPtr: &cmd.flagRunAsUser}, + {env: "AGENT_INJECT_RUN_AS_GROUP", value: "1001", cmdPtr: &cmd.flagRunAsGroup}, } for _, tt := range tests {