Skip to content

Commit

Permalink
feat: save YAML spec used to generate support bundle/preflight (#1713)
Browse files Browse the repository at this point in the history
* save YAML spec of support bundle

* save YAML spec of preflight

* add unit test

* redact TLS private key by default in output spec

* update YAML path for HTTP TLS redactor
  • Loading branch information
nvanthao authored Jan 4, 2025
1 parent 64ee9e5 commit f6f51ac
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 0 deletions.
2 changes: 2 additions & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
29 changes: 29 additions & 0 deletions pkg/preflight/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
}
14 changes: 14 additions & 0 deletions pkg/redact/redact.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
48 changes: 48 additions & 0 deletions pkg/supportbundle/supportbundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
74 changes: 74 additions & 0 deletions pkg/supportbundle/supportbundle_test.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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)
}

0 comments on commit f6f51ac

Please sign in to comment.