diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 1fd9ce3d4..791a0ef48 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -11,6 +11,8 @@ const ( DEFAULT_CLIENT_USER_AGENT = "ReplicatedTroubleshoot" // VERSION_FILENAME is the name of the file that contains the support bundle version. VERSION_FILENAME = "version.yaml" + // SPEC_FILENAME is the name of the file that contains the support bundle/preflight spec. + SPEC_FILENAME = "spec.yaml" // DEFAULT_LOGS_COLLECTOR_TIMEOUT is the default timeout for logs collector. DEFAULT_LOGS_COLLECTOR_TIMEOUT = 60 * time.Second // MAX_TIME_TO_WAIT_FOR_POD_DELETION is the maximum time to wait for pod deletion. diff --git a/pkg/preflight/run.go b/pkg/preflight/run.go index cceda9238..222981d3b 100644 --- a/pkg/preflight/run.go +++ b/pkg/preflight/run.go @@ -20,6 +20,7 @@ import ( "github.com/replicatedhq/troubleshoot/pkg/constants" "github.com/replicatedhq/troubleshoot/pkg/convert" "github.com/replicatedhq/troubleshoot/pkg/k8sutil" + "github.com/replicatedhq/troubleshoot/pkg/loader" "github.com/replicatedhq/troubleshoot/pkg/types" "github.com/replicatedhq/troubleshoot/pkg/version" "github.com/spf13/viper" @@ -180,6 +181,13 @@ func RunPreflights(interactive bool, output string, format string, args []string return errors.Wrap(err, "failed to save version file") } + // save final preflight spec used to geneate the preflight checks + err = savePreflightSpecToBundle(specs, collectorResults, bundlePath) + if err != nil { + // still allow the preflight to be created + klog.Errorf("failed to save preflight YAML spec: %v", err) + } + analyzeResults, err := analyzer.AnalyzeLocal(ctx, bundlePath, analyzers, hostAnalyzers) if err != nil { return errors.Wrap(err, "failed to analyze support bundle") @@ -469,3 +477,24 @@ func parseTimeFlags(v *viper.Viper, collectors []*troubleshootv1beta2.Collect) e } return nil } + +func savePreflightSpecToBundle(specs *loader.TroubleshootKinds, result collect.CollectorResult, bundlePath string) error { + yamlContent, err := specs.ToYaml() + if err != nil { + return errors.Wrap(err, "failed to convert preflight specs to yaml") + } + err = result.SaveResult(bundlePath, constants.SPEC_FILENAME, bytes.NewBuffer([]byte(yamlContent))) + if err != nil { + return errors.Wrap(err, "failed to write preflight spec to bundle") + } + // redact the final YAML spec + singleResult := map[string][]byte{ + constants.SPEC_FILENAME: []byte(yamlContent), + } + + err = collect.RedactResult(bundlePath, singleResult, nil) + if err != nil { + return errors.Wrap(err, "failed to redact final preflight yaml spec") + } + return nil +} diff --git a/pkg/redact/redact.go b/pkg/redact/redact.go index 3823a9dbe..67b214f76 100644 --- a/pkg/redact/redact.go +++ b/pkg/redact/redact.go @@ -470,6 +470,20 @@ func getRedactors(path string) ([]Redactor, error) { } } + // redact final YAML spec used to generate the support bundle/preflight + // redact TLS private key if any + // todo: any other TLS keys to redact? + tlsKeys := []string{"clientKey"} + for _, key := range tlsKeys { + yamlPaths := []string{ + fmt.Sprintf("spec.collectors.*.*.tls.%s", key), // Database collector + fmt.Sprintf("spec.collectors.*.*.*.tls.%s", key), // HTTP collector + } + for _, yamlPath := range yamlPaths { + redactors = append(redactors, NewYamlRedactor(yamlPath, constants.SPEC_FILENAME, "Redact TLS private key")) + } + } + return redactors, nil } diff --git a/pkg/supportbundle/supportbundle.go b/pkg/supportbundle/supportbundle.go index 31f28deed..bdc645fe4 100644 --- a/pkg/supportbundle/supportbundle.go +++ b/pkg/supportbundle/supportbundle.go @@ -20,6 +20,7 @@ import ( "github.com/replicatedhq/troubleshoot/pkg/collect" "github.com/replicatedhq/troubleshoot/pkg/constants" "github.com/replicatedhq/troubleshoot/pkg/convert" + "github.com/replicatedhq/troubleshoot/pkg/loader" "github.com/replicatedhq/troubleshoot/pkg/version" "go.opentelemetry.io/otel" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -165,6 +166,13 @@ func CollectSupportBundleFromSpec( return nil, errors.Wrap(err, "failed to write version") } + // save final YAML spec used to geneate the support bundle + err = saveAndRedactFinalSpec(spec, &result, bundlePath, additionalRedactors) + if err != nil { + // still allow the support bundle to be created + klog.Errorf("failed to save and redact final spec: %v", err) + } + // Run Analyzers analyzeResults, err := AnalyzeSupportBundle(ctx, spec, bundlePath) if err != nil { @@ -312,3 +320,43 @@ func getNodeList(clientset kubernetes.Interface, opts SupportBundleCreateOpts) ( return &nodeList, nil } + +func saveAndRedactFinalSpec(spec *troubleshootv1beta2.SupportBundleSpec, result *collect.CollectorResult, bundlePath string, additionalRedactors *troubleshootv1beta2.Redactor) error { + // generate the final YAML spec + k := loader.TroubleshootKinds{ + SupportBundlesV1Beta2: []troubleshootv1beta2.SupportBundle{ + { + TypeMeta: metav1.TypeMeta{ + APIVersion: "troubleshoot.sh/v1beta2", + Kind: "SupportBundle", + }, + Spec: *spec, + }, + }, + } + yamlContent, err := k.ToYaml() + if err != nil { + return errors.Wrap(err, "failed to convert final support bundle spec to yaml") + } + + err = result.SaveResult(bundlePath, constants.SPEC_FILENAME, bytes.NewBuffer([]byte(yamlContent))) + if err != nil { + return errors.Wrap(err, "failed to write final support bundle yaml spec") + } + + // redact the final YAML spec + singleResult := map[string][]byte{ + constants.SPEC_FILENAME: []byte(yamlContent), + } + + var redactors []*troubleshootv1beta2.Redact + if additionalRedactors != nil { + redactors = additionalRedactors.Spec.Redactors + } + err = collect.RedactResult(bundlePath, singleResult, redactors) + if err != nil { + return errors.Wrap(err, "failed to redact final support bundle yaml spec") + } + + return nil +} diff --git a/pkg/supportbundle/supportbundle_test.go b/pkg/supportbundle/supportbundle_test.go index 55c8dfd31..7cfa79200 100644 --- a/pkg/supportbundle/supportbundle_test.go +++ b/pkg/supportbundle/supportbundle_test.go @@ -1,10 +1,17 @@ package supportbundle import ( + "os" + "path/filepath" "reflect" "testing" troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" + "github.com/replicatedhq/troubleshoot/pkg/collect" + "github.com/replicatedhq/troubleshoot/pkg/constants" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -114,3 +121,70 @@ func Test_getNodeList(t *testing.T) { }) } } + +func Test_saveAndRedactFinalSpec(t *testing.T) { + spec := &troubleshootv1beta2.SupportBundleSpec{ + Collectors: []*troubleshootv1beta2.Collect{ + { + ClusterInfo: &troubleshootv1beta2.ClusterInfo{}, + ClusterResources: &troubleshootv1beta2.ClusterResources{}, + Postgres: &troubleshootv1beta2.Database{ + URI: "postgresql://user:password@hostname:5432/defaultdb?sslmode=require", + TLS: &troubleshootv1beta2.TLSParams{ + CACert: `CA CERT`, + ClientCert: `CLIENT CERT`, + ClientKey: `PRIVATE KEY`, + }, + }, + HTTP: &troubleshootv1beta2.HTTP{ + Get: &troubleshootv1beta2.Get{ + URL: "http:api:3000/healthz", + TLS: &troubleshootv1beta2.TLSParams{ + ClientKey: `PRIVATE KEY`, + }, + }, + }, + }, + }, + } + + result := make(collect.CollectorResult) + bundlePath := t.TempDir() + expectedYAML := ` +apiVersion: troubleshoot.sh/v1beta2 +kind: SupportBundle +metadata: + creationTimestamp: null +spec: + collectors: + - clusterInfo: {} + clusterResources: {} + postgres: + uri: postgresql://***HIDDEN***:***HIDDEN***@***HIDDEN***:5432/***HIDDEN*** + tls: + cacert: CA CERT + clientCert: CLIENT CERT + clientKey: "***HIDDEN***" + http: + get: + url: http:api:3000/healthz + tls: + clientKey: "***HIDDEN***" +status: {} +` + + err := saveAndRedactFinalSpec(spec, &result, bundlePath, nil) + if err != nil { + t.Fatal(err) + } + + actualYAML, err := os.ReadFile(filepath.Join(bundlePath, constants.SPEC_FILENAME)) + require.NoError(t, err) + + var expectedData, actualData interface{} + err = yaml.Unmarshal([]byte(expectedYAML), &expectedData) + require.NoError(t, err) + err = yaml.Unmarshal(actualYAML, &actualData) + require.NoError(t, err) + assert.Equal(t, expectedData, actualData) +}