Skip to content

Commit 8250838

Browse files
Merge pull request #3046 from czeslavo/must-not-gather-secrets
Make must-gather not collect sensitive resources by default
2 parents 3f422f0 + c44db8e commit 8250838

File tree

9 files changed

+835
-371
lines changed

9 files changed

+835
-371
lines changed

docs/source/support/must-gather.md

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# Gathering data with must-gather
22

33
`must-gather` is an embedded tool in Scylla Operator that helps collecting all the necessary info when something goes wrong.
4-
54
The tool talks to the Kubernetes API, retrieves a predefined set of resources and saves them into a folder in your current directory.
6-
By default, all collected Secrets are censored to avoid sending sensitive data.
7-
That said, you can always review the archive before you attach it to an issue or your support request.
85

9-
Given it needs to talk to the Kubernetes API, at the very least, you need to supply the `--kubeconfig` flag with a path to the kubeconfig file for your Kubernetes cluster, or set the `KUBECONFIG` environment variable.
6+
By default, sensitive resources (`Secrets` and `bitnami.com.SealedSecrets`) are omitted from the collection.
7+
If you come across a clear need to include them (not recommended unless the default settings are deemed insufficient for investigation), please refer to the [include sensitive resources](#including-sensitive-resources) section below.
8+
If you have other resources that you wouldn't like to include in the collection, refer to the [excluding resources](#excluding-resources) section below.
9+
10+
Given that `must-gather` needs to talk to the Kubernetes API, at the very least, you need to supply the `--kubeconfig` flag with a path to the kubeconfig file for your Kubernetes cluster, or set the `KUBECONFIG` environment variable.
1011

1112
## Running must-gather
1213

@@ -99,3 +100,25 @@ You can also request collecting every resource in the Kubernetes API, if the def
99100
```bash
100101
scylla-operator must-gather --all-resources
101102
```
103+
104+
### Including sensitive resources
105+
106+
By default, `must-gather` omits sensitive resources like `Secrets` or `bitnami.com.SealedSecrets`. You can turn this
107+
behavior off by passing the `--include-sensitive-resources` flag.
108+
109+
```bash
110+
scylla-operator must-gather --all-resources --include-sensitive-resources
111+
```
112+
113+
### Excluding resources
114+
115+
If you have resources that you don't want to include in the collection other than the default ones, you can exclude them
116+
by passing the `--exclude-resource` flag multiple times.
117+
118+
:::{note}
119+
The format for the resource is `kind` (for core resources) or `kind.group`. Examples: `LimitRange` or `DeviceClass.resource.k8s.io`.
120+
:::
121+
122+
```bash
123+
scylla-operator must-gather --all-resources --exclude-resource="LimitRange" --exclude-resource="DeviceClass.resource.k8s.io"
124+
```

pkg/cmd/operator/gatherbase.go

Lines changed: 91 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import (
66
"math"
77
"os"
88
"path/filepath"
9+
"strings"
910

1011
"github.com/scylladb/scylla-operator/pkg/cmdutil"
1112
"github.com/scylladb/scylla-operator/pkg/gather/collect"
1213
"github.com/scylladb/scylla-operator/pkg/genericclioptions"
1314
"github.com/spf13/cobra"
1415
"github.com/spf13/pflag"
16+
"k8s.io/apimachinery/pkg/runtime/schema"
1517
apimachineryutilerrors "k8s.io/apimachinery/pkg/util/errors"
1618
apimachineryutilrand "k8s.io/apimachinery/pkg/util/rand"
1719
kgenericclioptions "k8s.io/cli-runtime/pkg/genericclioptions"
@@ -24,22 +26,61 @@ import (
2426
"k8s.io/klog/v2"
2527
)
2628

29+
const (
30+
excludeResourceFlagHelpFormat = `Kubernetes resource to exclude, in the format kind[.group]. Can be specified multiple times.
31+
Kind matching is case-insensitive; omit the group only for core resources (e.g., Pod, ConfigMap").
32+
By default, the following are always excluded, despite the value of this flag: %s
33+
34+
Specifying this flag adds to the default exclusions. For example:
35+
--exclude-resource=Pod --exclude-resource=Deployment.apps
36+
37+
To force-include resources that are excluded by default, use --include-sensitive-resources`
38+
39+
includeSensitiveResourcesFlagHelpFormat = `Controls whether sensitive resources (%s) should be collected. (default false)
40+
When this flag is set, you can still exclude selected ones using --exclude-resource.
41+
For example:
42+
--include-sensitive-resources --exclude-resource=Secret
43+
will only exclude Secrets.`
44+
)
45+
46+
// GatherBaseCLIFlags holds the command-line flags for the gather base options.
47+
// These are raw values directly from the command line. They need to be validated and
48+
// processed before use.
49+
type GatherBaseCLIFlags struct {
50+
DestDir string
51+
CollectManagedFields bool
52+
LogsLimitBytes int64
53+
KeepGoing bool
54+
ExcludeResources []string
55+
IncludeSensitiveResources bool
56+
}
57+
58+
// NewGatherBaseCLIFlags creates a new GatherBaseCLIFlags with default values.
59+
func NewGatherBaseCLIFlags() *GatherBaseCLIFlags {
60+
return &GatherBaseCLIFlags{
61+
DestDir: "",
62+
LogsLimitBytes: 0,
63+
CollectManagedFields: false,
64+
KeepGoing: true,
65+
ExcludeResources: []string{},
66+
IncludeSensitiveResources: false,
67+
}
68+
}
69+
2770
type GatherBaseOptions struct {
2871
GathererName string
2972
ConfigFlags *kgenericclioptions.ConfigFlags
3073

31-
restConfig *rest.Config
32-
kubeClient kubernetes.Interface
33-
dynamicClient dynamic.Interface
34-
discoveryClient discovery.DiscoveryInterface
74+
restConfig *rest.Config
75+
kubeClient kubernetes.Interface
76+
dynamicClient dynamic.Interface
77+
discoveryClient discovery.DiscoveryInterface
78+
resourcesToExclude []schema.GroupKind
3579

36-
DestDir string
37-
CollectManagedFields bool
38-
LogsLimitBytes int64
39-
KeepGoing bool
80+
cliFlags *GatherBaseCLIFlags
4081
}
4182

42-
func NewGatherBaseOptions(gathererName string, keepGoing bool) *GatherBaseOptions {
83+
func NewGatherBaseOptions(gathererName string) *GatherBaseOptions {
4384
return &GatherBaseOptions{
4485
GathererName: gathererName,
4586
ConfigFlags: kgenericclioptions.NewConfigFlags(true).WithWrapConfigFn(func(c *rest.Config) *rest.Config {
@@ -49,44 +90,47 @@ func NewGatherBaseOptions(gathererName string, keepGoing bool) *GatherBaseOption
4990
c.Burst = math.MaxInt
5091
return c
5192
}),
52-
DestDir: "",
53-
CollectManagedFields: false,
54-
LogsLimitBytes: 0,
55-
KeepGoing: keepGoing,
93+
cliFlags: NewGatherBaseCLIFlags(),
5694
}
5795
}
5896

5997
func (o *GatherBaseOptions) AddFlags(flagset *pflag.FlagSet) {
6098
o.ConfigFlags.AddFlags(flagset)
6199

62-
flagset.StringVarP(&o.DestDir, "dest-dir", "", o.DestDir, "Destination directory where to store the artifacts.")
63-
flagset.Int64VarP(&o.LogsLimitBytes, "log-limit-bytes", "", o.LogsLimitBytes, "Maximum number of bytes collected for each log file, 0 means unlimited.")
64-
flagset.BoolVarP(&o.CollectManagedFields, "managed-fields", "", o.CollectManagedFields, "Controls whether metadata.managedFields should be collected in the resource dumps.")
65-
flagset.BoolVarP(&o.KeepGoing, "keep-going", "", o.KeepGoing, "Controls whether the collection should proceed to other resources over collection errors, accumulating errors.")
100+
f := o.cliFlags
101+
flagset.StringVar(&f.DestDir, "dest-dir", f.DestDir, "Destination directory where to store the artifacts.")
102+
flagset.Int64Var(&f.LogsLimitBytes, "log-limit-bytes", f.LogsLimitBytes, "Maximum number of bytes collected for each log file, 0 means unlimited.")
103+
flagset.BoolVar(&f.CollectManagedFields, "managed-fields", f.CollectManagedFields, "Controls whether metadata.managedFields should be collected in the resource dumps.")
104+
flagset.BoolVar(&f.KeepGoing, "keep-going", f.KeepGoing, "Controls whether the collection should proceed to other resources over collection errors, accumulating errors.")
105+
flagset.StringArrayVar(&f.ExcludeResources, "exclude-resource", f.ExcludeResources, fmt.Sprintf(excludeResourceFlagHelpFormat, defaultExcludedSensitiveResourcesAsString()))
106+
flagset.BoolVar(&f.IncludeSensitiveResources, "include-sensitive-resources", f.IncludeSensitiveResources, fmt.Sprintf(includeSensitiveResourcesFlagHelpFormat, defaultExcludedSensitiveResourcesAsString()))
66107
}
67108

68109
func (o *GatherBaseOptions) Validate() error {
69110
var errs []error
70111

71-
if o.LogsLimitBytes < 0 {
72-
errs = append(errs, fmt.Errorf("log-limit-bytes can't be lower then 0 but %v has been specified", o.LogsLimitBytes))
112+
f := o.cliFlags
113+
if f.LogsLimitBytes < 0 {
114+
errs = append(errs, fmt.Errorf("log-limit-bytes can't be lower then 0 but %v has been specified", f.LogsLimitBytes))
73115
}
74116

75-
if len(o.DestDir) > 0 {
76-
files, err := os.ReadDir(o.DestDir)
117+
if len(f.DestDir) > 0 {
118+
files, err := os.ReadDir(f.DestDir)
77119
if err == nil {
78120
if len(files) > 0 {
79-
errs = append(errs, fmt.Errorf("destination directory %q is not empty", o.DestDir))
121+
errs = append(errs, fmt.Errorf("destination directory %q is not empty", f.DestDir))
80122
}
81123
} else if !os.IsNotExist(err) {
82-
errs = append(errs, fmt.Errorf("can't stat destination directory %q: %w", o.DestDir, err))
124+
errs = append(errs, fmt.Errorf("can't stat destination directory %q: %w", f.DestDir, err))
83125
}
84126
}
85127

86128
return apimachineryutilerrors.NewAggregate(errs)
87129
}
88130

89131
func (o *GatherBaseOptions) Complete() error {
132+
f := o.cliFlags
133+
90134
restConfig, err := o.ConfigFlags.ToRESTConfig()
91135
if err != nil {
92136
return fmt.Errorf("can't create RESTConfig: %w", err)
@@ -107,29 +151,35 @@ func (o *GatherBaseOptions) Complete() error {
107151
o.discoveryClient = cacheddiscovery.NewMemCacheClient(o.kubeClient.Discovery())
108152

109153
ignoreDestDirExists := false
110-
if len(o.DestDir) == 0 {
111-
o.DestDir = fmt.Sprintf("%s-%s", o.GathererName, apimachineryutilrand.String(12))
154+
if len(f.DestDir) == 0 {
155+
f.DestDir = fmt.Sprintf("%s-%s", o.GathererName, apimachineryutilrand.String(12))
112156
} else {
113157
// We have already made sure that the dir doesn't exist or is empty in validation.
114158
ignoreDestDirExists = true
115159
}
116160

117-
err = os.Mkdir(o.DestDir, 0770)
161+
err = os.Mkdir(f.DestDir, 0770)
118162
if err == nil {
119-
klog.InfoS("Created destination directory", "Path", o.DestDir)
163+
klog.InfoS("Created destination directory", "Path", f.DestDir)
120164
} else if os.IsExist(err) {
121165
// Just to be sure we cover it, but it's unlikely a dir with random suffix would already exist.
122166
if !ignoreDestDirExists {
123-
return fmt.Errorf("can't create destination directory %q because it already exists: %w", o.DestDir, err)
167+
return fmt.Errorf("can't create destination directory %q because it already exists: %w", f.DestDir, err)
124168
}
125169
} else {
126-
return fmt.Errorf("can't create destination directory %q: %w", o.DestDir, err)
170+
return fmt.Errorf("can't create destination directory %q: %w", f.DestDir, err)
171+
}
172+
173+
for _, exclude := range f.ExcludeResources {
174+
o.resourcesToExclude = append(o.resourcesToExclude, schema.ParseGroupKind(exclude))
127175
}
128176

129177
return nil
130178
}
131179

132180
func (o *GatherBaseOptions) RunInit(originalStreams genericclioptions.IOStreams, cmd *cobra.Command) error {
181+
f := o.cliFlags
182+
133183
err := flag.Set("logtostderr", "false")
134184
if err != nil {
135185
return fmt.Errorf("can't set logtostderr flag: %w", err)
@@ -140,7 +190,7 @@ func (o *GatherBaseOptions) RunInit(originalStreams genericclioptions.IOStreams,
140190
return fmt.Errorf("can't set alsologtostderr flag: %w", err)
141191
}
142192

143-
err = flag.Set("log_file", filepath.Join(o.DestDir, fmt.Sprintf("%s.log", o.GathererName)))
193+
err = flag.Set("log_file", filepath.Join(f.DestDir, fmt.Sprintf("%s.log", o.GathererName)))
144194
if err != nil {
145195
return fmt.Errorf("can't set log_file flag: %w", err)
146196
}
@@ -154,9 +204,10 @@ func (o *GatherBaseOptions) RunInit(originalStreams genericclioptions.IOStreams,
154204
}
155205

156206
func (o *GatherBaseOptions) GetPrinters() []collect.ResourcePrinterInterface {
207+
f := o.cliFlags
157208
printers := make([]collect.ResourcePrinterInterface, 0, 1)
158209

159-
if o.CollectManagedFields {
210+
if f.CollectManagedFields {
160211
printers = append(printers, &collect.YAMLPrinter{})
161212
} else {
162213
printers = append(printers, &collect.OmitManagedFieldsPrinter{
@@ -166,3 +217,12 @@ func (o *GatherBaseOptions) GetPrinters() []collect.ResourcePrinterInterface {
166217

167218
return printers
168219
}
220+
221+
// defaultExcludedSensitiveResourcesAsString returns a string representation of the default excluded sensitive resources for use in flag help.
222+
func defaultExcludedSensitiveResourcesAsString() string {
223+
var ss []string
224+
for _, gk := range collect.DefaultExcludedSensitiveResources() {
225+
ss = append(ss, gk.String())
226+
}
227+
return strings.Join(ss, ", ")
228+
}

0 commit comments

Comments
 (0)