mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-08-27 00:37:20 +00:00
fix(runpod): strip pod spec before saving collected output (#2105)
This commit is contained in:
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user