Skip to content

Commit

Permalink
Added support for IRSA, fixes - hashicorp#86
Browse files Browse the repository at this point in the history
  • Loading branch information
infa-mhadiman committed Aug 13, 2020
1 parent 23879d1 commit 1eaac1c
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 36 deletions.
66 changes: 53 additions & 13 deletions agent-inject/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ type Agent struct {
// SetSecurityContext controls whether the injected containers have a
// SecurityContext set.
SetSecurityContext bool

// AwsIamTokenAccountName is the aws iam volume mount name for the pod.
// Need this for IRSA aka pod identity
AwsIamTokenAccountName string

// AwsIamTokenAccountPath is the aws iam volume mount path for the pod
// where the JWT would be present
// Need this for IRSA aka pod identity
AwsIamTokenAccountPath string
}

type Secret struct {
Expand Down Expand Up @@ -213,21 +222,24 @@ type VaultAgentCache struct {
// New creates a new instance of Agent by parsing all the Kubernetes annotations.
func New(pod *corev1.Pod, patches []*jsonpatch.JsonPatchOperation) (*Agent, error) {
saName, saPath := serviceaccount(pod)
iamName, iamPath := getAwsIamTokenAccount(pod)

agent := &Agent{
Annotations: pod.Annotations,
ConfigMapName: pod.Annotations[AnnotationAgentConfigMap],
ImageName: pod.Annotations[AnnotationAgentImage],
LimitsCPU: pod.Annotations[AnnotationAgentLimitsCPU],
LimitsMem: pod.Annotations[AnnotationAgentLimitsMem],
Namespace: pod.Annotations[AnnotationAgentRequestNamespace],
Patches: patches,
Pod: pod,
RequestsCPU: pod.Annotations[AnnotationAgentRequestsCPU],
RequestsMem: pod.Annotations[AnnotationAgentRequestsMem],
ServiceAccountName: saName,
ServiceAccountPath: saPath,
Status: pod.Annotations[AnnotationAgentStatus],
Annotations: pod.Annotations,
ConfigMapName: pod.Annotations[AnnotationAgentConfigMap],
ImageName: pod.Annotations[AnnotationAgentImage],
LimitsCPU: pod.Annotations[AnnotationAgentLimitsCPU],
LimitsMem: pod.Annotations[AnnotationAgentLimitsMem],
Namespace: pod.Annotations[AnnotationAgentRequestNamespace],
Patches: patches,
Pod: pod,
RequestsCPU: pod.Annotations[AnnotationAgentRequestsCPU],
RequestsMem: pod.Annotations[AnnotationAgentRequestsMem],
ServiceAccountName: saName,
ServiceAccountPath: saPath,
AwsIamTokenAccountName: iamName,
AwsIamTokenAccountPath: iamPath,
Status: pod.Annotations[AnnotationAgentStatus],
Vault: Vault{
Address: pod.Annotations[AnnotationVaultService],
AuthPath: pod.Annotations[AnnotationVaultAuthPath],
Expand Down Expand Up @@ -508,6 +520,34 @@ func serviceaccount(pod *corev1.Pod) (string, string) {
return serviceAccountName, serviceAccountPath
}

// IRSA support - get aws_iam_token volume mount details to inject to vault containers
func getAwsIamTokenAccount(pod *corev1.Pod) (string, string) {
var awsIamTokenAccountName, awsIamTokenAccountPath string
for _, container := range pod.Spec.Containers {
for _, volumes := range container.VolumeMounts {
if strings.Contains(volumes.MountPath, "eks.amazonaws.com") {
return volumes.Name, volumes.MountPath
}
}
}
return awsIamTokenAccountName, awsIamTokenAccountPath
}

// IRSA support - get aws envs to inject to vault containers
func (a *Agent) getEnvsFromContainer(pod *corev1.Pod) map[string]string {
envMap := make(map[string]string)
for _, container := range pod.Spec.Containers {
for _, env := range container.Env {
if strings.Contains(env.Name, "AWS_ROLE_ARN") || strings.Contains(env.Name, "AWS_WEB_IDENTITY_TOKEN_FILE") {
if _, ok := envMap[env.Name]; !ok {
envMap[env.Name] = env.Value
}
}
}
}
return envMap
}

func (a *Agent) vaultCliFlags() []string {
flags := []string{
fmt.Sprintf("-address=%s", a.Vault.Address),
Expand Down
31 changes: 31 additions & 0 deletions agent-inject/agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,37 @@ func testPod(annotations map[string]string) *corev1.Pod {
}
}

func testPodIRSA(annotations map[string]string) *corev1.Pod {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Annotations: annotations,
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foobar",
VolumeMounts: []corev1.VolumeMount{
{
Name: "foobar",
MountPath: "serviceaccount/somewhere",
},
},
},
{
Name: "aws-iam-token",
VolumeMounts: []corev1.VolumeMount{
{
Name: "aws-iam-token",
MountPath: "/var/run/secrets/eks.amazonaws.com/serviceaccount",
},
},
},
},
},
}
}

func TestShouldInject(t *testing.T) {
tests := []struct {
annotations map[string]string
Expand Down
17 changes: 17 additions & 0 deletions agent-inject/agent/container_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package agent

import (
"encoding/base64"
"log"

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

Expand Down Expand Up @@ -42,6 +44,21 @@ func (a *Agent) ContainerEnvVars(init bool) ([]corev1.EnvVar, error) {
Name: "VAULT_CONFIG",
Value: b64Config,
})

// Add IRSA AWS Env variables for vault containers
if a.Pod != nil {
envMap := a.getEnvsFromContainer(a.Pod)
if len(envMap) == 0 || len(envMap) >= 2 {
for k, v := range envMap {
envs = append(envs, corev1.EnvVar{
Name: k,
Value: v,
})
}
} else {
log.Println("WARN: Could not find 'AWS ROLE/ AWS_WEB_IDENTITY_TOKEN_FILE' env variables")
}
}
}

return envs, nil
Expand Down
55 changes: 54 additions & 1 deletion agent-inject/agent/container_env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"testing"

"github.com/hashicorp/vault/sdk/helper/strutil"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestContainerEnvs(t *testing.T) {
Expand All @@ -25,7 +27,6 @@ func TestContainerEnvs(t *testing.T) {
if err != nil {
t.Errorf("got error, shouldn't have: %s", err)
}

if len(envs) != len(tt.expectedEnvs) {
t.Errorf("number of envs mismatch, wanted %d, got %d", len(tt.expectedEnvs), len(envs))
}
Expand All @@ -37,3 +38,55 @@ func TestContainerEnvs(t *testing.T) {
}
}
}

func TestContainerEnvsForIRSA(t *testing.T) {
envTests := []struct {
agent Agent
expectedEnvs []string
}{
{Agent{Pod: testPodWithoutIRSA()}, []string{"VAULT_CONFIG"}},
{Agent{Pod: testPodWithIRSA()}, []string{"VAULT_CONFIG", "AWS_ROLE_ARN", "AWS_WEB_IDENTITY_TOKEN_FILE"}},
}
for _, tt := range envTests {
envs, err := tt.agent.ContainerEnvVars(true)
if err != nil {
t.Errorf("got error, shouldn't have: %s", err)
}
if len(envs) != len(tt.expectedEnvs) {
t.Errorf("number of envs mismatch, wanted %d, got %d", len(tt.expectedEnvs), len(envs))
}
}
}

func testPodWithoutIRSA() *corev1.Pod {
return testPodWithEnv(nil)
}

func testPodWithIRSA() *corev1.Pod {
return testPodWithEnv([]corev1.EnvVar{
{
Name: "AWS_ROLE_ARN",
Value: "foorole",
},
{
Name: "AWS_WEB_IDENTITY_TOKEN_FILE",
Value: "footoken",
},
})
}

func testPodWithEnv(envVars []corev1.EnvVar) *corev1.Pod {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foobar",
Env: envVars,
},
},
},
}
}
45 changes: 34 additions & 11 deletions agent-inject/agent/container_init_sidecar.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,40 @@ import (
// available for the agent. This means we won't need to generate
// 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,
ReadOnly: true,
},
volumeMounts := []corev1.VolumeMount{}
// Add aws token volume to init sidecar
if a.AwsIamTokenAccountName == "" || a.AwsIamTokenAccountPath == "" {
volumeMounts = []corev1.VolumeMount{
{
Name: tokenVolumeName,
MountPath: tokenVolumePath,
ReadOnly: false,
},
{
Name: a.ServiceAccountName,
MountPath: a.ServiceAccountPath,
ReadOnly: true,
},
}
} else {
volumeMounts = []corev1.VolumeMount{
{
Name: tokenVolumeName,
MountPath: tokenVolumePath,
ReadOnly: false,
},
{
Name: a.ServiceAccountName,
MountPath: a.ServiceAccountPath,
ReadOnly: true,
},
// add aws volume mounts to be available for init sidecar
{
Name: a.AwsIamTokenAccountName,
MountPath: a.AwsIamTokenAccountPath,
ReadOnly: true,
},
}
}
volumeMounts = append(volumeMounts, a.ContainerVolumeMounts()...)

Expand Down
44 changes: 33 additions & 11 deletions agent-inject/agent/container_sidecar.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,39 @@ const (
// ContainerSidecar creates a new container to be added
// to the pod being mutated.
func (a *Agent) ContainerSidecar() (corev1.Container, error) {
volumeMounts := []corev1.VolumeMount{
{
Name: a.ServiceAccountName,
MountPath: a.ServiceAccountPath,
ReadOnly: true,
},
{
Name: tokenVolumeName,
MountPath: tokenVolumePath,
ReadOnly: false,
},
volumeMounts := []corev1.VolumeMount{}
if a.AwsIamTokenAccountName == "" || a.AwsIamTokenAccountPath == "" {
volumeMounts = []corev1.VolumeMount{
{
Name: tokenVolumeName,
MountPath: tokenVolumePath,
ReadOnly: false,
},
{
Name: a.ServiceAccountName,
MountPath: a.ServiceAccountPath,
ReadOnly: true,
},
}
} else {
volumeMounts = []corev1.VolumeMount{
{
Name: tokenVolumeName,
MountPath: tokenVolumePath,
ReadOnly: false,
},
{
Name: a.ServiceAccountName,
MountPath: a.ServiceAccountPath,
ReadOnly: true,
},
// add aws volume mounts to be available for sidecar
{
Name: a.AwsIamTokenAccountName,
MountPath: a.AwsIamTokenAccountPath,
ReadOnly: true,
},
}
}
volumeMounts = append(volumeMounts, a.ContainerVolumeMounts()...)

Expand Down
Loading

0 comments on commit 1eaac1c

Please sign in to comment.