Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow agent container RunAsUser/Group to be specified via annotations #60

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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