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
…hashicorp#60)

* allow agent container RunAsUser/Group to be specified via annotations

* set run-as-{user,group} globally in the injector via environment variables

* emptyDir volume for token sink (/home/vault) in init and sidecar containers
  • Loading branch information
joemiller authored Mar 17, 2020
1 parent ecad994 commit fcbcbba
Show file tree
Hide file tree
Showing 12 changed files with 347 additions and 62 deletions.
28 changes: 26 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.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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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(
Expand Down
52 changes: 41 additions & 11 deletions agent-inject/agent/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")
}

Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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
}

Expand Down
69 changes: 57 additions & 12 deletions agent-inject/agent/annotations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package agent
import (
"fmt"
"reflect"
"strconv"
"strings"
"testing"

Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand All @@ -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 {
Expand All @@ -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)

}
}
}
Expand All @@ -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")
}
Expand All @@ -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")
}
Expand All @@ -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")
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
},
},
},
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -438,14 +466,26 @@ 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 {
annotations := map[string]string{tt.key: tt.value}
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 {
Expand All @@ -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")
}
}

Expand All @@ -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)
Expand Down
Loading

0 comments on commit fcbcbba

Please sign in to comment.