diff --git a/agent-inject/agent/agent.go b/agent-inject/agent/agent.go index e99bc2f1..9a655717 100644 --- a/agent-inject/agent/agent.go +++ b/agent-inject/agent/agent.go @@ -14,8 +14,10 @@ import ( // TODO swap out 'github.com/mattbaird/jsonpatch' for 'github.com/evanphx/json-patch' const ( - DefaultVaultImage = "vault:1.3.2" - DefaultVaultAuthPath = "auth/kubernetes" + DefaultVaultImage = "vault:1.3.2" + DefaultVaultAuthPath = "auth/kubernetes" + DefaultAgentRunAsUser = 100 + DefaultAgentRunAsGroup = 1000 ) // Agent is the top level structure holding all the @@ -96,6 +98,12 @@ type Agent struct { // Vault is the structure holding all the Vault specific configurations. Vault Vault + + // RunAsUser is the user ID to run the Vault agent container(s) as. + RunAsUser int64 + + // RunAsGroup is the group ID to run the Vault agent container(s) as. + RunAsGroup int64 } type Secret struct { @@ -239,6 +247,16 @@ func New(pod *corev1.Pod, patches []*jsonpatch.JsonPatchOperation) (*Agent, erro return agent, err } + agent.RunAsUser, err = strconv.ParseInt(pod.Annotations[AnnotationAgentRunAsUser], 10, 64) + if err != nil { + return agent, err + } + + agent.RunAsGroup, err = strconv.ParseInt(pod.Annotations[AnnotationAgentRunAsGroup], 10, 64) + if err != nil { + return agent, err + } + return agent, nil } @@ -280,6 +298,12 @@ func ShouldInject(pod *corev1.Pod) (bool, error) { func (a *Agent) Patch() ([]byte, error) { var patches []byte + // Add a volume for the token sink + a.Patches = append(a.Patches, addVolumes( + a.Pod.Spec.Volumes, + []corev1.Volume{a.ContainerTokenVolume()}, + "/spec/volumes")...) + // Add our volume that will be shared by the containers // for passing data in the pod. a.Patches = append(a.Patches, addVolumes( diff --git a/agent-inject/agent/annotations.go b/agent-inject/agent/annotations.go index 73f0df72..43464d14 100644 --- a/agent-inject/agent/annotations.go +++ b/agent-inject/agent/annotations.go @@ -95,6 +95,12 @@ const ( // AnnotationVaultNamespace is the Vault namespace where secrets can be found. AnnotationVaultNamespace = "vault.hashicorp.com/namespace" + // AnnotationAgentRunAsUser sets the User ID to run the Vault Agent containers as. + AnnotationAgentRunAsUser = "vault.hashicorp.com/agent-run-as-user" + + // AnnotationAgentRunAsGroup sets the Group ID to run the Vault Agent containers as. + AnnotationAgentRunAsGroup = "vault.hashicorp.com/agent-run-as-group" + // AnnotationVaultService is the name of the Vault server. This can be overridden by the // user but will be set by a flag on the deployment. AnnotationVaultService = "vault.hashicorp.com/service" @@ -152,23 +158,33 @@ const ( AnnotationPreserveSecretCase = "vault.hashicorp.com/preserve-secret-case" ) +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") } @@ -177,22 +193,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 { @@ -216,7 +232,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 { @@ -227,6 +243,20 @@ func Init(pod *corev1.Pod, image, address, authPath, namespace string, revokeOnS pod.ObjectMeta.Annotations[AnnotationVaultLogLevel] = DefaultAgentLogLevel } + if _, ok := pod.ObjectMeta.Annotations[AnnotationAgentRunAsUser]; !ok { + if cfg.UserID == "" { + cfg.UserID = strconv.Itoa(DefaultAgentRunAsUser) + } + pod.ObjectMeta.Annotations[AnnotationAgentRunAsUser] = cfg.UserID + } + + if _, ok := pod.ObjectMeta.Annotations[AnnotationAgentRunAsGroup]; !ok { + 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 4160e7e6..36628c44 100644 --- a/agent-inject/agent/annotations_test.go +++ b/agent-inject/agent/annotations_test.go @@ -3,6 +3,7 @@ package agent import ( "fmt" "reflect" + "strconv" "strings" "testing" @@ -15,7 +16,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 +48,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) } @@ -57,6 +58,8 @@ func TestInitDefaults(t *testing.T) { annotationValue string }{ {annotationKey: AnnotationAgentImage, annotationValue: DefaultVaultImage}, + {annotationKey: AnnotationAgentRunAsUser, annotationValue: strconv.Itoa(DefaultAgentRunAsUser)}, + {annotationKey: AnnotationAgentRunAsGroup, annotationValue: strconv.Itoa(DefaultAgentRunAsGroup)}, } for _, tt := range tests { @@ -67,7 +70,6 @@ func TestInitDefaults(t *testing.T) { if raw != tt.annotationValue { t.Errorf("Default annotation value incorrect, wanted %s, got %s", tt.annotationValue, raw) - } } } @@ -76,7 +78,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") } @@ -86,7 +88,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") } @@ -96,7 +98,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") } @@ -132,6 +134,11 @@ func TestSecretAnnotationsWithPreserveCaseSensitivityFlagOff(t *testing.T) { pod := testPod(annotation) var patches []*jsonpatch.JsonPatchOperation + err := Init(pod, AgentConfig{"", "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) @@ -174,6 +181,11 @@ func TestSecretAnnotationsWithPreserveCaseSensitivityFlagOn(t *testing.T) { pod := testPod(annotation) var patches []*jsonpatch.JsonPatchOperation + err := Init(pod, AgentConfig{"", "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) @@ -245,6 +257,11 @@ func TestSecretTemplateAnnotations(t *testing.T) { pod := testPod(tt.annotations) var patches []*jsonpatch.JsonPatchOperation + err := Init(pod, AgentConfig{"", "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) @@ -277,9 +294,10 @@ func TestTemplateShortcuts(t *testing.T) { }, map[string]Secret{ "token": Secret{ - Name: "token", - Path: TokenSecret, - Template: TokenTemplate, + Name: "token", + Path: TokenSecret, + Template: TokenTemplate, + MountPath: secretVolumePath, }, }, }, @@ -295,6 +313,11 @@ func TestTemplateShortcuts(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pod := testPod(tt.annotations) + err := Init(pod, AgentConfig{"", "http://foobar:8200", "test", "test", true, "1000", "100"}) + if err != nil { + t.Errorf("got error, shouldn't have: %s", err) + } + var patches []*jsonpatch.JsonPatchOperation agent, err := New(pod, patches) @@ -346,6 +369,11 @@ func TestSecretCommandAnnotations(t *testing.T) { for _, tt := range tests { pod := testPod(tt.annotations) + err := Init(pod, AgentConfig{"", "http://foobar:8200", "test", "test", true, "1000", "100"}) + if err != nil { + t.Errorf("got error, shouldn't have: %s", err) + } + var patches []*jsonpatch.JsonPatchOperation agent, err := New(pod, patches) @@ -438,6 +466,13 @@ func TestCouldErrorAnnotations(t *testing.T) { {AnnotationAgentRevokeGrace, "01", true}, {AnnotationAgentRevokeGrace, "-1", false}, {AnnotationAgentRevokeGrace, "foobar", false}, + {AnnotationAgentRunAsUser, "0", true}, + {AnnotationAgentRunAsUser, "100", true}, + {AnnotationAgentRunAsUser, "root", false}, + + {AnnotationAgentRunAsGroup, "0", true}, + {AnnotationAgentRunAsGroup, "100", true}, + {AnnotationAgentRunAsGroup, "root", false}, } for i, tt := range tests { @@ -445,7 +480,12 @@ func TestCouldErrorAnnotations(t *testing.T) { pod := testPod(annotations) var patches []*jsonpatch.JsonPatchOperation - _, err := New(pod, patches) + err := Init(pod, AgentConfig{"", "http://foobar:8200", "test", "test", true, "1000", "100"}) + if err != nil { + t.Errorf("got error, shouldn't have: %s", err) + } + + _, err = New(pod, patches) if err != nil && tt.valid { t.Errorf("[%d] got error, shouldn't have: %s", i, err) } else if err == nil && !tt.valid { @@ -457,9 +497,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") } } @@ -482,6 +522,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_init_sidecar.go b/agent-inject/agent/container_init_sidecar.go index 9bc7bb66..94cf9813 100644 --- a/agent-inject/agent/container_init_sidecar.go +++ b/agent-inject/agent/container_init_sidecar.go @@ -2,7 +2,7 @@ package agent import ( "fmt" - "github.com/hashicorp/vault/sdk/helper/pointerutil" + corev1 "k8s.io/api/core/v1" ) @@ -13,6 +13,11 @@ import ( // two config files. func (a *Agent) ContainerInitSidecar() (corev1.Container, error) { volumeMounts := []corev1.VolumeMount{ + { + Name: tokenVolumeName, + MountPath: tokenVolumePath, + ReadOnly: false, + }, { Name: a.ServiceAccountName, MountPath: a.ServiceAccountPath, @@ -51,17 +56,13 @@ func (a *Agent) ContainerInitSidecar() (corev1.Container, error) { } return corev1.Container{ - Name: "vault-agent-init", - Image: a.ImageName, - Env: envs, - Resources: resources, - SecurityContext: &corev1.SecurityContext{ - RunAsUser: pointerutil.Int64Ptr(100), - RunAsGroup: pointerutil.Int64Ptr(1000), - RunAsNonRoot: pointerutil.BoolPtr(true), - }, - VolumeMounts: volumeMounts, - Command: []string{"/bin/sh", "-ec"}, - Args: []string{arg}, + Name: "vault-agent-init", + Image: a.ImageName, + Env: envs, + Resources: resources, + SecurityContext: a.securityContext(), + VolumeMounts: volumeMounts, + Command: []string{"/bin/sh", "-ec"}, + Args: []string{arg}, }, nil } diff --git a/agent-inject/agent/container_sidecar.go b/agent-inject/agent/container_sidecar.go index 120bae32..d152b12b 100644 --- a/agent-inject/agent/container_sidecar.go +++ b/agent-inject/agent/container_sidecar.go @@ -30,6 +30,11 @@ func (a *Agent) ContainerSidecar() (corev1.Container, error) { MountPath: a.ServiceAccountPath, ReadOnly: true, }, + { + Name: tokenVolumeName, + MountPath: tokenVolumePath, + ReadOnly: false, + }, } volumeMounts = append(volumeMounts, a.ContainerVolumeMounts()...) @@ -65,19 +70,15 @@ func (a *Agent) ContainerSidecar() (corev1.Container, error) { lifecycle := a.createLifecycle() return corev1.Container{ - Name: "vault-agent", - Image: a.ImageName, - Env: envs, - Resources: resources, - SecurityContext: &corev1.SecurityContext{ - RunAsUser: pointerutil.Int64Ptr(100), - RunAsGroup: pointerutil.Int64Ptr(1000), - RunAsNonRoot: pointerutil.BoolPtr(true), - }, - Lifecycle: &lifecycle, - VolumeMounts: volumeMounts, - Command: []string{"/bin/sh", "-ec"}, - Args: []string{arg}, + Name: "vault-agent", + Image: a.ImageName, + Env: envs, + Resources: resources, + SecurityContext: a.securityContext(), + VolumeMounts: volumeMounts, + Lifecycle: &lifecycle, + Command: []string{"/bin/sh", "-ec"}, + Args: []string{arg}, }, nil } @@ -116,7 +117,6 @@ func (a *Agent) parseResources() (corev1.ResourceRequirements, error) { resources.Requests = requests return resources, nil - } func parseQuantity(raw string) (resource.Quantity, error) { @@ -145,3 +145,15 @@ func (a *Agent) createLifecycle() corev1.Lifecycle { return lifecycle } + +func (a *Agent) securityContext() *corev1.SecurityContext { + runAsNonRoot := true + if a.RunAsUser == 0 || a.RunAsGroup == 0 { + runAsNonRoot = false + } + return &corev1.SecurityContext{ + RunAsUser: pointerutil.Int64Ptr(a.RunAsUser), + RunAsGroup: pointerutil.Int64Ptr(a.RunAsGroup), + RunAsNonRoot: pointerutil.BoolPtr(runAsNonRoot), + } +} diff --git a/agent-inject/agent/container_sidecar_test.go b/agent-inject/agent/container_sidecar_test.go index 5a6cd059..ebff2690 100644 --- a/agent-inject/agent/container_sidecar_test.go +++ b/agent-inject/agent/container_sidecar_test.go @@ -2,6 +2,7 @@ package agent import ( "fmt" + "strconv" "testing" "github.com/mattbaird/jsonpatch" @@ -31,7 +32,7 @@ func TestContainerSidecarVolume(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) } @@ -43,8 +44,8 @@ func TestContainerSidecarVolume(t *testing.T) { container, err := agent.ContainerSidecar() - // One config volume mount and two secrets volume mounts - require.Equal(t, 3, len(container.VolumeMounts)) + // One token volume mount, one config volume mount and two secrets volume mounts + require.Equal(t, 4, len(container.VolumeMounts)) require.Equal( t, @@ -54,6 +55,11 @@ func TestContainerSidecarVolume(t *testing.T) { MountPath: agent.ServiceAccountPath, ReadOnly: true, }, + corev1.VolumeMount{ + Name: tokenVolumeName, + MountPath: tokenVolumePath, + ReadOnly: false, + }, corev1.VolumeMount{ Name: secretVolumeName, MountPath: agent.Annotations[AnnotationVaultSecretVolumePath], @@ -77,7 +83,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) } @@ -178,7 +184,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) } @@ -227,7 +233,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) } @@ -520,3 +526,92 @@ func TestContainerSidecarCustomResources(t *testing.T) { }) } } + +func TestContainerSidecarSecurityContext(t *testing.T) { + tests := []struct { + name string + runAsUser int + runAsGroup int + expectedRunAsUser int64 + expectedRunAsGroup int64 + expectedRunAsNonRoot bool + }{ + { + name: "Defaults", + runAsUser: DefaultAgentRunAsUser, + runAsGroup: DefaultAgentRunAsGroup, + expectedRunAsUser: DefaultAgentRunAsUser, + expectedRunAsGroup: DefaultAgentRunAsGroup, + expectedRunAsNonRoot: true, + }, + { + name: "non-root user and non-root group", + runAsUser: 1001, + runAsGroup: 1001, + expectedRunAsUser: 1001, + expectedRunAsGroup: 1001, + expectedRunAsNonRoot: true, + }, + { + name: "root user and group", + runAsUser: 0, + runAsGroup: 0, + expectedRunAsUser: 0, + expectedRunAsGroup: 0, + expectedRunAsNonRoot: false, + }, + { + name: "root user and non-root group", + runAsUser: 0, + runAsGroup: 100, + expectedRunAsUser: 0, + expectedRunAsGroup: 100, + expectedRunAsNonRoot: false, + }, + { + name: "non-root user and root group", + runAsUser: 100, + runAsGroup: 0, + expectedRunAsUser: 100, + expectedRunAsGroup: 0, + expectedRunAsNonRoot: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + annotations := map[string]string{ + AnnotationVaultRole: "foobar", + AnnotationAgentRunAsUser: strconv.Itoa(tt.runAsUser), + AnnotationAgentRunAsGroup: strconv.Itoa(tt.runAsGroup), + } + pod := testPod(annotations) + var patches []*jsonpatch.JsonPatchOperation + + 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) + } + + agent, err := New(pod, patches) + if err := agent.Validate(); err != nil { + t.Errorf("agent validation failed, it shouldn't have: %s", err) + } + + container, err := agent.ContainerSidecar() + if err != nil { + t.Errorf("got error, shouldn't have: %s", err) + } + + if *container.SecurityContext.RunAsUser != tt.expectedRunAsUser { + t.Errorf("expected RunAsUser mismatch: wanted %d, got %d", tt.expectedRunAsUser, *container.SecurityContext.RunAsUser) + } + if *container.SecurityContext.RunAsGroup != tt.expectedRunAsGroup { + t.Errorf("expected RunAsGroup mismatch: wanted %d, got %d", tt.expectedRunAsGroup, *container.SecurityContext.RunAsGroup) + } + if *container.SecurityContext.RunAsNonRoot != tt.expectedRunAsNonRoot { + t.Errorf("expected RunAsNonRoot mismatch: wanted %t, got %t", tt.expectedRunAsNonRoot, *container.SecurityContext.RunAsNonRoot) + } + }) + } +} diff --git a/agent-inject/agent/container_volume.go b/agent-inject/agent/container_volume.go index a6716d83..052c4f92 100644 --- a/agent-inject/agent/container_volume.go +++ b/agent-inject/agent/container_volume.go @@ -8,6 +8,8 @@ import ( ) const ( + tokenVolumeName = "home" + tokenVolumePath = "/home/vault" configVolumeName = "vault-config" configVolumePath = "/vault/configs" secretVolumeName = "vault-secrets" @@ -56,6 +58,19 @@ func (a *Agent) ContainerVolumes() []corev1.Volume { return containerVolumes } +// ContainerTokenVolume returns a volume to mount the +// home directory where the token sink will write to. +func (a *Agent) ContainerTokenVolume() corev1.Volume { + return corev1.Volume{ + Name: tokenVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + Medium: "Memory", + }, + }, + } +} + // ContainerConfigMapVolume returns a volume to mount a config map // if the user supplied any. func (a *Agent) ContainerConfigMapVolume() corev1.Volume { 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/agent-inject/handler_test.go b/agent-inject/handler_test.go index 9bc7dd0e..e57ea43b 100644 --- a/agent-inject/handler_test.go +++ b/agent-inject/handler_test.go @@ -138,6 +138,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/spec/volumes", }, + { + Operation: "add", + Path: "/spec/volumes", + }, { Operation: "add", Path: "/spec/containers/0/volumeMounts/-", @@ -169,8 +173,8 @@ func TestHandlerHandle(t *testing.T) { Object: encodeRaw(t, &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - agent.AnnotationAgentInject: "true", - agent.AnnotationVaultRole: "demo", + agent.AnnotationAgentInject: "true", + agent.AnnotationVaultRole: "demo", agent.AnnotationAgentInitFirst: "true", }, }, @@ -183,6 +187,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/spec/volumes", }, + { + Operation: "add", + Path: "/spec/volumes", + }, { Operation: "add", Path: "/spec/containers/0/volumeMounts/-", @@ -239,6 +247,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/spec/volumes", }, + { + Operation: "add", + Path: "/spec/volumes", + }, { Operation: "add", Path: "/spec/containers/0/volumeMounts/-", @@ -292,6 +304,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/spec/volumes", }, + { + Operation: "add", + Path: "/spec/volumes", + }, { Operation: "add", Path: "/spec/containers/0/volumeMounts/-", @@ -341,6 +357,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/spec/volumes", }, + { + Operation: "add", + Path: "/spec/volumes", + }, { Operation: "add", Path: "/spec/containers/0/volumeMounts/-", @@ -391,6 +411,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/spec/volumes", }, + { + Operation: "add", + Path: "/spec/volumes", + }, { Operation: "add", Path: "/spec/containers/0/volumeMounts/-", @@ -433,6 +457,10 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/spec/volumes", }, + { + Operation: "add", + Path: "/spec/volumes", + }, { Operation: "add", Path: "/spec/containers/0/volumeMounts/-", 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 b19885cd..5af67357 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 {