fix(runpod): strip pod spec before saving collected output (#2105)

This commit is contained in:
Xav Paice
2026-08-12 09:13:40 +12:00
committed by GitHub
parent 651ec1bfdb
commit 0e2e0cd0ab
2 changed files with 105 additions and 3 deletions
+8 -3
View File
@@ -69,7 +69,7 @@ func (c *CollectRunPod) Collect(progressChan chan<- interface{}) (result Collect
if err != nil {
return
}
result, err = savePodDetails(ctx, client, result, c.BundlePath, c.ClientConfig, pod, c.Collector)
result, err = savePodDetails(ctx, client, result, c.BundlePath, pod, c.Collector)
if err != nil {
klog.Errorf("failed to save pod details: %v", err)
}
@@ -407,7 +407,7 @@ func RunPodLogsWithOptions(ctx context.Context, client v1.CoreV1Interface, podSp
return io.ReadAll(logs)
}
func savePodDetails(ctx context.Context, client *kubernetes.Clientset, output CollectorResult, bundlePath string, clientConfig *rest.Config, pod *corev1.Pod, runPodCollector *troubleshootv1beta2.RunPod) (CollectorResult, error) {
func savePodDetails(ctx context.Context, client kubernetes.Interface, output CollectorResult, bundlePath string, pod *corev1.Pod, runPodCollector *troubleshootv1beta2.RunPod) (CollectorResult, error) {
podStatus, err := client.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{})
if err != nil {
return nil, errors.Wrap(err, "failed to get pod")
@@ -418,7 +418,12 @@ func savePodDetails(ctx context.Context, client *kubernetes.Clientset, output Co
return nil, errors.Wrap(err, "failed to get pod events")
}
podBytes, err := json.MarshalIndent(podStatus, "", " ")
// The full pod Spec can contain sensitive data such as env vars, commands, args,
// volumes, and image pull secrets. Strip it before saving the pod to the bundle.
sanitizedPod := *podStatus
sanitizedPod.Spec = corev1.PodSpec{}
podBytes, err := json.MarshalIndent(sanitizedPod, "", " ")
if err != nil {
return nil, errors.Wrap(err, "failed to marshal pod status")
}
+97
View File
@@ -2,6 +2,7 @@ package collect
import (
"context"
"encoding/json"
"testing"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
@@ -239,3 +240,99 @@ func Test_deleteImagePullSecret(t *testing.T) {
})
}
}
func TestSavePodDetails_StripsPodSpec(t *testing.T) {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-run-pod",
Namespace: "default",
Labels: map[string]string{
"troubleshoot-role": "run-collector",
},
},
Spec: corev1.PodSpec{
NodeName: "test-node",
Containers: []corev1.Container{
{
Name: "collector",
Image: "busybox",
Command: []string{"sh", "-c", "echo secret-command"},
Args: []string{"--token", "super-secret-token"},
Env: []corev1.EnvVar{
{Name: "PASSWORD", Value: "hunter2"},
{
Name: "API_KEY",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: "my-secret"},
Key: "api-key",
},
},
},
},
},
},
ImagePullSecrets: []corev1.LocalObjectReference{{Name: "my-pull-secret"}},
Volumes: []corev1.Volume{
{
Name: "secret-vol",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{SecretName: "my-secret"},
},
},
},
},
Status: corev1.PodStatus{
Phase: corev1.PodSucceeded,
ContainerStatuses: []corev1.ContainerStatus{
{
Name: "collector",
State: corev1.ContainerState{
Terminated: &corev1.ContainerStateTerminated{ExitCode: 0},
},
},
},
},
}
client := fake.NewSimpleClientset(pod)
collector := &troubleshootv1beta2.RunPod{
Name: "test-collector",
Namespace: "default",
}
result, err := savePodDetails(context.Background(), client, NewResult(), "", pod, collector)
require.NoError(t, err)
podKey := "test-collector/test-collector.json"
require.Contains(t, result, podKey)
saved := string(result[podKey])
// Debugging info must still be present
assert.Contains(t, saved, `"name": "test-run-pod"`)
assert.Contains(t, saved, `"phase": "Succeeded"`)
assert.Contains(t, saved, `"exitCode": 0`)
// Sensitive spec data must not be present
assert.NotContains(t, saved, "hunter2")
assert.NotContains(t, saved, "super-secret-token")
assert.NotContains(t, saved, "secret-command")
assert.NotContains(t, saved, "my-secret")
assert.NotContains(t, saved, "my-pull-secret")
assert.NotContains(t, saved, `"command":`)
assert.NotContains(t, saved, `"args":`)
assert.NotContains(t, saved, `"env":`)
// The saved JSON must still unmarshal into a Pod so downstream consumers
// (e.g. goldpinger) can read Name and Status.ContainerStatuses.
var savedPod corev1.Pod
require.NoError(t, json.Unmarshal(result[podKey], &savedPod))
assert.Equal(t, "test-run-pod", savedPod.Name)
assert.Equal(t, corev1.PodSucceeded, savedPod.Status.Phase)
assert.Len(t, savedPod.Status.ContainerStatuses, 1)
assert.Equal(t, int32(0), savedPod.Status.ContainerStatuses[0].State.Terminated.ExitCode)
assert.Empty(t, savedPod.Spec.Containers)
assert.Empty(t, savedPod.Spec.Volumes)
assert.Empty(t, savedPod.Spec.ImagePullSecrets)
}