Skip to content

Commit

Permalink
feat: Add support to auto-mount service account tokens for plugins. (#…
Browse files Browse the repository at this point in the history
…8176)

Signed-off-by: Alex Collins <alex_collins@intuit.com>
  • Loading branch information
alexec committed Apr 27, 2022
1 parent 9c08aed commit 32b1b3a
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 21 deletions.
4 changes: 3 additions & 1 deletion pkg/plugins/spec/plugin_types.go
Expand Up @@ -25,7 +25,9 @@ type PluginSpec struct {
}

type Sidecar struct {
Container apiv1.Container `json:"container"`
// AutomountServiceAccount mounts the service account's token. The service account must have the same name as the plugin.
AutomountServiceAccountToken bool `json:"automountServiceAccountToken,omitempty"`
Container apiv1.Container `json:"container"`
}

func (s Sidecar) Validate() error {
Expand Down
40 changes: 31 additions & 9 deletions test/e2e/executor_plugins_test.go
Expand Up @@ -41,16 +41,38 @@ func (s *ExecutorPluginsSuite) TestTemplateExecutor() {
RunAsUser: pointer.Int64(8737),
RunAsNonRoot: pointer.BoolPtr(true),
}, spec.SecurityContext)
if assert.Len(t, spec.Volumes, 4) {
assert.Contains(t, spec.Volumes[0].Name, "kube-api-access-")
assert.Equal(t, spec.Volumes[1].Name, "var-run-argo")
assert.Contains(t, spec.Volumes[2].Name, "kube-api-access-")
assert.Equal(t, spec.Volumes[3].Name, "argo-workflows-agent-ca-certificates")
}
if assert.Len(t, spec.Containers, 2) {
agent := spec.Containers[1]
if assert.Equal(t, "main", agent.Name) {
assert.Equal(t, &apiv1.SecurityContext{
RunAsUser: pointer.Int64(8737),
RunAsNonRoot: pointer.BoolPtr(true),
AllowPrivilegeEscalation: pointer.BoolPtr(false),
ReadOnlyRootFilesystem: pointer.BoolPtr(true),
Capabilities: &apiv1.Capabilities{Drop: []apiv1.Capability{"ALL"}},
}, agent.SecurityContext)
{
plug := spec.Containers[0]
if assert.Equal(t, "hello-executor-plugin", plug.Name) {
if assert.Len(t, plug.VolumeMounts, 2) {
assert.Equal(t, "var-run-argo", plug.VolumeMounts[0].Name)
assert.Contains(t, plug.VolumeMounts[1].Name, "kube-api-access-")
}
}
}
{
agent := spec.Containers[1]
if assert.Equal(t, "main", agent.Name) {
if assert.Len(t, agent.VolumeMounts, 3) {
assert.Equal(t, "var-run-argo", agent.VolumeMounts[0].Name)
assert.Contains(t, agent.VolumeMounts[1].Name, "kube-api-access-")
assert.Equal(t, agent.VolumeMounts[2].Name, "argo-workflows-agent-ca-certificates")
}
assert.Equal(t, &apiv1.SecurityContext{
RunAsUser: pointer.Int64(8737),
RunAsNonRoot: pointer.BoolPtr(true),
AllowPrivilegeEscalation: pointer.BoolPtr(false),
ReadOnlyRootFilesystem: pointer.BoolPtr(true),
Capabilities: &apiv1.Capabilities{Drop: []apiv1.Capability{"ALL"}},
}, agent.SecurityContext)
}
}
}
}
Expand Down
@@ -1,6 +1,6 @@
# This is an auto-generated file. DO NOT EDIT
apiVersion: v1
data:
sidecar.automountServiceAccountToken: "true"
sidecar.container: |
args:
- |
Expand Down
@@ -0,0 +1,4 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: hello-executor-plugin
1 change: 1 addition & 0 deletions test/e2e/manifests/plugins/kustomization.yaml
Expand Up @@ -3,6 +3,7 @@ kind: Kustomization

resources:
- ../minimal
- hello-executor-plugin-serviceaccount.yaml
- hello-executor-plugin-configmap.yaml

commonLabels:
Expand Down
27 changes: 21 additions & 6 deletions workflow/controller/agent.go
Expand Up @@ -128,7 +128,11 @@ func (woc *wfOperationCtx) createAgentPod(ctx context.Context) (*apiv1.Pod, erro
return nil, err
}

pluginSidecars := woc.getExecutorPlugins()
pluginSidecars, pluginVolumes, err := woc.getExecutorPlugins(ctx)
if err != nil {
return nil, err
}

envVars := []apiv1.EnvVar{
{Name: common.EnvVarWorkflowName, Value: woc.wf.Name},
{Name: common.EnvAgentPatchRate, Value: env.LookupEnvStringOr(common.EnvAgentPatchRate, GetRequeueTime().String())},
Expand All @@ -150,10 +154,11 @@ func (woc *wfOperationCtx) createAgentPod(ctx context.Context) (*apiv1.Pod, erro
return nil, fmt.Errorf("failed to get token volumes: %w", err)
}

podVolumes := []apiv1.Volume{
podVolumes := append(
pluginVolumes,
volumeVarArgo,
*tokenVolume,
}
)
podVolumeMounts := []apiv1.VolumeMount{
volumeMountVarArgo,
*tokenVolumeMount,
Expand Down Expand Up @@ -251,25 +256,35 @@ func (woc *wfOperationCtx) createAgentPod(ctx context.Context) (*apiv1.Pod, erro
return created, nil
}

func (woc *wfOperationCtx) getExecutorPlugins() []apiv1.Container {
func (woc *wfOperationCtx) getExecutorPlugins(ctx context.Context) ([]apiv1.Container, []apiv1.Volume, error) {
var sidecars []apiv1.Container
var volumes []apiv1.Volume
namespaces := map[string]bool{} // de-dupes executorPlugins when their namespaces are the same
namespaces[woc.controller.namespace] = true
namespaces[woc.wf.Namespace] = true
for namespace := range namespaces {
for _, plug := range woc.controller.executorPlugins[namespace] {
c := plug.Spec.Sidecar.Container.DeepCopy()
s := plug.Spec.Sidecar
c := s.Container.DeepCopy()
c.VolumeMounts = append(c.VolumeMounts, apiv1.VolumeMount{
Name: volumeMountVarArgo.Name,
MountPath: volumeMountVarArgo.MountPath,
ReadOnly: true,
// only mount the token for this plugin, not others
SubPath: c.Name,
})
if s.AutomountServiceAccountToken {
volume, volumeMount, err := woc.getServiceAccountTokenVolume(ctx, plug.Name+"-executor-plugin")
if err != nil {
return nil, nil, err
}
volumes = append(volumes, *volume)
c.VolumeMounts = append(c.VolumeMounts, *volumeMount)
}
sidecars = append(sidecars, *c)
}
}
return sidecars
return sidecars, volumes, nil
}

func addresses(containers []apiv1.Container) []string {
Expand Down
4 changes: 3 additions & 1 deletion workflow/util/plugins/configmap.go
Expand Up @@ -34,7 +34,8 @@ func ToConfigMap(p *spec.Plugin) (*apiv1.ConfigMap, error) {
Namespace: p.Namespace,
},
Data: map[string]string{
"sidecar.container": string(data),
"sidecar.automountServiceAccountToken": fmt.Sprint(p.Spec.Sidecar.AutomountServiceAccountToken),
"sidecar.container": string(data),
},
}
for k, v := range p.Annotations {
Expand Down Expand Up @@ -64,6 +65,7 @@ func FromConfigMap(cm *apiv1.ConfigMap) (*spec.Plugin, error) {
p.Labels[k] = v
}
delete(p.Labels, common.LabelKeyConfigMapType)
p.Spec.Sidecar.AutomountServiceAccountToken = cm.Data["sidecar.automountServiceAccountToken"] == "true"
if err := yaml.UnmarshalStrict([]byte(cm.Data["sidecar.container"]), &p.Spec.Sidecar.Container); err != nil {
return nil, err
}
Expand Down
9 changes: 6 additions & 3 deletions workflow/util/plugins/configmap_test.go
Expand Up @@ -33,6 +33,7 @@ func TestToConfigMap(t *testing.T) {
},
Spec: spec.PluginSpec{
Sidecar: spec.Sidecar{
AutomountServiceAccountToken: true,
Container: apiv1.Container{
Ports: []apiv1.ContainerPort{{ContainerPort: 1234}},
Resources: apiv1.ResourceRequirements{
Expand All @@ -52,7 +53,8 @@ func TestToConfigMap(t *testing.T) {
"workflows.argoproj.io/configmap-type": "ExecutorPlugin",
}, cm.Labels)
assert.Equal(t, map[string]string{
"sidecar.container": "name: \"\"\nports:\n- containerPort: 1234\nresources: {}\nsecurityContext: {}\n",
"sidecar.automountServiceAccountToken": "true",
"sidecar.container": "name: \"\"\nports:\n- containerPort: 1234\nresources: {}\nsecurityContext: {}\n",
}, cm.Data)
}
})
Expand All @@ -76,15 +78,16 @@ func TestFromConfigMap(t *testing.T) {
},
},
Data: map[string]string{
"sidecar.address": "http://my-addr",
"sidecar.container": "{'name': 'my-name', 'ports': [{}], 'resources': {'requests': {}, 'limits': {}}, 'securityContext': {}}",
"sidecar.automountServiceAccountToken": "true",
"sidecar.container": "{'name': 'my-name', 'ports': [{}], 'resources': {'requests': {}, 'limits': {}}, 'securityContext': {}}",
},
})
if assert.NoError(t, err) {
assert.Equal(t, "ExecutorPlugin", p.Kind)
assert.Equal(t, "my-plug", p.Name)
assert.Len(t, p.Annotations, 1)
assert.Len(t, p.Labels, 1)
assert.True(t, p.Spec.Sidecar.AutomountServiceAccountToken)
assert.Equal(t, apiv1.Container{
Name: "my-name",
Ports: []apiv1.ContainerPort{{}},
Expand Down

0 comments on commit 32b1b3a

Please sign in to comment.