Skip to content

Commit

Permalink
allow agent container RunAsUser/Group to be specified via annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
joemiller committed Feb 20, 2020
1 parent 0884c6b commit a3db366
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 28 deletions.
22 changes: 20 additions & 2 deletions agent-inject/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import (
// TODO swap out 'github.com/mattbaird/jsonpatch' for 'github.com/evanphx/json-patch'

const (
DefaultVaultImage = "vault:1.3.1"
DefaultVaultAuthPath = "auth/kubernetes"
DefaultVaultImage = "vault:1.3.1"
DefaultVaultAuthPath = "auth/kubernetes"
DefaultAgentRunAsUser = 100
DefaultAgentRunAsGroup = 1000
)

// Agent is the top level structure holding all the
Expand Down Expand Up @@ -85,6 +87,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 {
Expand Down Expand Up @@ -202,6 +210,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
}

Expand Down
14 changes: 14 additions & 0 deletions agent-inject/agent/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ const (
// AnnotationAgentRequestsMem sets the requested memory amount on the Vault Agent containers.
AnnotationAgentRequestsMem = "vault.hashicorp.com/agent-requests-mem"

// 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"
Expand Down Expand Up @@ -184,6 +190,14 @@ func Init(pod *corev1.Pod, image, address, authPath, namespace string) error {
pod.ObjectMeta.Annotations[AnnotationAgentRequestsMem] = DefaultResourceRequestMem
}

if _, ok := pod.ObjectMeta.Annotations[AnnotationAgentRunAsUser]; !ok {
pod.ObjectMeta.Annotations[AnnotationAgentRunAsUser] = strconv.Itoa(DefaultAgentRunAsUser)
}

if _, ok := pod.ObjectMeta.Annotations[AnnotationAgentRunAsGroup]; !ok {
pod.ObjectMeta.Annotations[AnnotationAgentRunAsGroup] = strconv.Itoa(DefaultAgentRunAsGroup)
}

return nil
}

Expand Down
38 changes: 37 additions & 1 deletion agent-inject/agent/annotations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package agent

import (
"reflect"
"strconv"
"strings"
"testing"

Expand Down Expand Up @@ -55,6 +56,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 {
Expand Down Expand Up @@ -127,6 +130,11 @@ func TestSecretAnnotations(t *testing.T) {
pod := testPod(annotation)
var patches []*jsonpatch.JsonPatchOperation

err := Init(pod, "", "http://foobar:8200", "test", "test")
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)
Expand Down Expand Up @@ -200,6 +208,11 @@ func TestSecretTemplateAnnotations(t *testing.T) {
pod := testPod(tt.annotations)
var patches []*jsonpatch.JsonPatchOperation

err := Init(pod, "", "http://foobar:8200", "test", "test")
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)
Expand Down Expand Up @@ -250,6 +263,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, "", "http://foobar:8200", "test", "test")
if err != nil {
t.Errorf("got error, shouldn't have: %s", err)
}

var patches []*jsonpatch.JsonPatchOperation

agent, err := New(pod, patches)
Expand Down Expand Up @@ -301,6 +319,11 @@ func TestSecretCommandAnnotations(t *testing.T) {

for _, tt := range tests {
pod := testPod(tt.annotations)
err := Init(pod, "", "http://foobar:8200", "test", "test")
if err != nil {
t.Errorf("got error, shouldn't have: %s", err)
}

var patches []*jsonpatch.JsonPatchOperation

agent, err := New(pod, patches)
Expand Down Expand Up @@ -375,14 +398,27 @@ func TestCouldErrorAnnotations(t *testing.T) {
{AnnotationVaultTLSSkipVerify, "tRuE", false},
{AnnotationVaultTLSSkipVerify, "fAlSe", false},
{AnnotationVaultTLSSkipVerify, "", false},

{AnnotationAgentRunAsUser, "0", true},
{AnnotationAgentRunAsUser, "100", true},
{AnnotationAgentRunAsUser, "root", false},

{AnnotationAgentRunAsGroup, "0", true},
{AnnotationAgentRunAsGroup, "100", true},
{AnnotationAgentRunAsGroup, "root", false},
}

for i, tt := range tests {
annotations := map[string]string{tt.key: tt.value}
pod := testPod(annotations)
var patches []*jsonpatch.JsonPatchOperation

_, err := New(pod, patches)
err := Init(pod, "", "http://foobar:8200", "test", "test")
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 {
Expand Down
22 changes: 9 additions & 13 deletions agent-inject/agent/container_init_sidecar.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package agent

import (
"fmt"
"github.com/hashicorp/vault/sdk/helper/pointerutil"

corev1 "k8s.io/api/core/v1"
)

Expand Down Expand Up @@ -55,17 +55,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
}
33 changes: 21 additions & 12 deletions agent-inject/agent/container_sidecar.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package agent

import (
"fmt"

"k8s.io/apimachinery/pkg/api/resource"

"github.com/hashicorp/vault/sdk/helper/pointerutil"
Expand Down Expand Up @@ -63,18 +64,14 @@ func (a *Agent) ContainerSidecar() (corev1.Container, error) {
}

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),
},
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,
Command: []string{"/bin/sh", "-ec"},
Args: []string{arg},
}, nil
}

Expand Down Expand Up @@ -124,3 +121,15 @@ func parseQuantity(raw string) (resource.Quantity, error) {

return resource.ParseQuantity(raw)
}

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),
}
}
90 changes: 90 additions & 0 deletions agent-inject/agent/container_sidecar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package agent

import (
"fmt"
"strconv"
"testing"

"github.com/mattbaird/jsonpatch"
Expand Down Expand Up @@ -385,3 +386,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, "foobar-image", "http://foobar:1234", "test", "test")
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)
}
})
}
}

0 comments on commit a3db366

Please sign in to comment.