Skip to content

Commit 600a675

Browse files
authored
Merge pull request #107 from replicatedhq/laverya/rbac-wip
Check RBAC before running collectors
2 parents 0993f6e + 55f2ed4 commit 600a675

File tree

10 files changed

+480
-106
lines changed

10 files changed

+480
-106
lines changed

cmd/preflight/cli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ that a cluster meets the requirements to run an application.`,
4242
cmd.Flags().String("pullpolicy", "", "the pull policy of the preflight image")
4343
cmd.Flags().String("collector-image", "", "the full name of the collector image to use")
4444
cmd.Flags().String("collector-pullpolicy", "", "the pull policy of the collector image")
45+
cmd.Flags().Bool("collect-without-permissions", false, "always run preflight checks even if some require permissions that preflight does not have")
4546

4647
cmd.Flags().String("serviceaccount", "", "name of the service account to use. if not provided, one will be created")
4748

cmd/preflight/cli/run.go

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"time"
1313

1414
"github.com/ahmetalpbalkan/go-cursor"
15+
"github.com/fatih/color"
1516
"github.com/pkg/errors"
1617
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
1718
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
@@ -65,22 +66,35 @@ func runPreflights(v *viper.Viper, arg string) error {
6566

6667
s := spin.New()
6768
finishedCh := make(chan bool, 1)
69+
progressChan := make(chan interface{}, 0) // non-zero buffer will result in missed messages
6870
go func() {
6971
for {
7072
select {
71-
case <-finishedCh:
72-
fmt.Printf("\r")
73-
return
73+
case msg, ok := <-progressChan:
74+
if !ok {
75+
continue
76+
}
77+
switch msg := msg.(type) {
78+
case error:
79+
c := color.New(color.FgHiRed)
80+
c.Println(fmt.Sprintf("%s\r * %v", cursor.ClearEntireLine(), msg))
81+
case string:
82+
c := color.New(color.FgCyan)
83+
c.Println(fmt.Sprintf("%s\r * %s", cursor.ClearEntireLine(), msg))
84+
}
7485
case <-time.After(time.Millisecond * 100):
7586
fmt.Printf("\r \033[36mRunning Preflight checks\033[m %s ", s.Next())
87+
case <-finishedCh:
88+
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
89+
return
7690
}
7791
}
7892
}()
7993
defer func() {
80-
finishedCh <- true
94+
close(finishedCh)
8195
}()
8296

83-
allCollectedData, err := runCollectors(v, preflight)
97+
allCollectedData, err := runCollectors(v, preflight, progressChan)
8498
if err != nil {
8599
return err
86100
}
@@ -117,25 +131,30 @@ func runPreflights(v *viper.Viper, arg string) error {
117131
}
118132
}
119133

120-
finishedCh <- true
121-
122134
if preflight.Spec.UploadResultsTo != "" {
123-
tryUploadResults(preflight.Spec.UploadResultsTo, preflight.Name, analyzeResults)
135+
err := uploadResults(preflight.Spec.UploadResultsTo, analyzeResults)
136+
if err != nil {
137+
progressChan <- err
138+
}
124139
}
140+
141+
finishedCh <- true
142+
125143
if v.GetBool("interactive") {
144+
if len(analyzeResults) == 0 {
145+
return errors.New("no data has been collected")
146+
}
126147
return showInteractiveResults(preflight.Name, analyzeResults)
127148
}
128149

129150
return showStdoutResults(v.GetString("format"), preflight.Name, analyzeResults)
130151
}
131152

132-
func runCollectors(v *viper.Viper, preflight troubleshootv1beta1.Preflight) (map[string][]byte, error) {
133-
desiredCollectors := make([]*troubleshootv1beta1.Collect, 0, 0)
134-
for _, definedCollector := range preflight.Spec.Collectors {
135-
desiredCollectors = append(desiredCollectors, definedCollector)
136-
}
137-
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
138-
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
153+
func runCollectors(v *viper.Viper, preflight troubleshootv1beta1.Preflight, progressChan chan interface{}) (map[string][]byte, error) {
154+
collectSpecs := make([]*troubleshootv1beta1.Collect, 0, 0)
155+
collectSpecs = append(collectSpecs, preflight.Spec.Collectors...)
156+
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
157+
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
139158

140159
allCollectedData := make(map[string][]byte)
141160

@@ -144,24 +163,56 @@ func runCollectors(v *viper.Viper, preflight troubleshootv1beta1.Preflight) (map
144163
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
145164
}
146165

147-
// Run preflights collectors synchronously
148-
for _, desiredCollector := range desiredCollectors {
166+
var collectors collect.Collectors
167+
for _, desiredCollector := range collectSpecs {
149168
collector := collect.Collector{
150169
Redact: true,
151170
Collect: desiredCollector,
152171
ClientConfig: config,
153172
Namespace: v.GetString("namespace"),
154173
}
174+
collectors = append(collectors, &collector)
175+
}
176+
177+
if err := collectors.CheckRBAC(); err != nil {
178+
return nil, errors.Wrap(err, "failed to check RBAC for collectors")
179+
}
180+
181+
foundForbidden := false
182+
for _, c := range collectors {
183+
for _, e := range c.RBACErrors {
184+
foundForbidden = true
185+
progressChan <- e
186+
}
187+
}
188+
189+
if foundForbidden && !v.GetBool("collect-without-permissions") {
190+
if preflight.Spec.UploadResultsTo != "" {
191+
err := uploadErrors(preflight.Spec.UploadResultsTo, collectors)
192+
if err != nil {
193+
progressChan <- err
194+
}
195+
}
196+
return nil, errors.New("insufficient permissions to run all collectors")
197+
}
198+
199+
// Run preflights collectors synchronously
200+
for _, collector := range collectors {
201+
if len(collector.RBACErrors) > 0 {
202+
continue
203+
}
155204

156205
result, err := collector.RunCollectorSync()
157206
if err != nil {
158-
return nil, errors.Wrap(err, "failed to run collector")
207+
progressChan <- errors.Errorf("failed to run collector %s: %v\n", collector.GetDisplayName(), err)
208+
continue
159209
}
160210

161211
if result != nil {
162212
output, err := parseCollectorOutput(string(result))
163213
if err != nil {
164-
return nil, errors.Wrap(err, "failed to parse collector output")
214+
progressChan <- errors.Errorf("failed to parse collector output %s: %v\n", collector.GetDisplayName(), err)
215+
continue
165216
}
166217
for k, v := range output {
167218
allCollectedData[k] = v

cmd/preflight/cli/upload_results.go

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55
"encoding/json"
66
"net/http"
77

8+
"github.com/pkg/errors"
89
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
10+
"github.com/replicatedhq/troubleshoot/pkg/collect"
911
)
1012

1113
type UploadPreflightResult struct {
@@ -18,12 +20,17 @@ type UploadPreflightResult struct {
1820
URI string `json:"uri,omitempty"`
1921
}
2022

23+
type UploadPreflightError struct {
24+
Error string `json:"error"`
25+
}
26+
2127
type UploadPreflightResults struct {
22-
Results []*UploadPreflightResult `json:"results"`
28+
Results []*UploadPreflightResult `json:"results,omitempty"`
29+
Errors []*UploadPreflightError `json:"errors,omitempty"`
2330
}
2431

25-
func tryUploadResults(uri string, preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
26-
uploadPreflightResults := UploadPreflightResults{
32+
func uploadResults(uri string, analyzeResults []*analyzerunner.AnalyzeResult) error {
33+
uploadPreflightResults := &UploadPreflightResults{
2734
Results: []*UploadPreflightResult{},
2835
}
2936
for _, analyzeResult := range analyzeResults {
@@ -39,26 +46,47 @@ func tryUploadResults(uri string, preflightName string, analyzeResults []*analyz
3946
uploadPreflightResults.Results = append(uploadPreflightResults.Results, uploadPreflightResult)
4047
}
4148

42-
b, err := json.Marshal(uploadPreflightResults)
49+
return upload(uri, uploadPreflightResults)
50+
}
51+
52+
func uploadErrors(uri string, collectors collect.Collectors) error {
53+
errors := []*UploadPreflightError{}
54+
for _, collector := range collectors {
55+
for _, e := range collector.RBACErrors {
56+
errors = append(errors, &UploadPreflightError{
57+
Error: e.Error(),
58+
})
59+
}
60+
}
61+
62+
results := &UploadPreflightResults{
63+
Errors: errors,
64+
}
65+
66+
return upload(uri, results)
67+
}
68+
69+
func upload(uri string, payload *UploadPreflightResults) error {
70+
b, err := json.Marshal(payload)
4371
if err != nil {
44-
return err
72+
return errors.Wrap(err, "failed to marshal payload")
4573
}
4674

4775
req, err := http.NewRequest("POST", uri, bytes.NewBuffer(b))
4876
if err != nil {
49-
return err
77+
return errors.Wrap(err, "failed to create request")
5078
}
5179

5280
req.Header.Set("Content-Type", "application/json")
5381

5482
client := http.DefaultClient
5583
resp, err := client.Do(req)
5684
if err != nil {
57-
return err
85+
return errors.Wrap(err, "failed to execute request")
5886
}
5987

6088
if resp.StatusCode > 290 {
61-
return err
89+
return errors.Errorf("unexpected status code: %d", resp.StatusCode)
6290
}
6391

6492
return nil

cmd/troubleshoot/cli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ from a server that can be used to assist when troubleshooting a server.`,
4343
cmd.Flags().String("image", "", "the full name of the collector image to use")
4444
cmd.Flags().String("pullpolicy", "", "the pull policy of the collector image")
4545
cmd.Flags().Bool("redact", true, "enable/disable default redactions")
46+
cmd.Flags().Bool("collect-without-permissions", false, "always run troubleshoot collectors even if some require permissions that troubleshoot does not have")
4647

4748
cmd.Flags().String("serviceaccount", "", "name of the service account to use. if not provided, one will be created")
4849
viper.BindPFlags(cmd.Flags())

cmd/troubleshoot/cli/run.go

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@ import (
1111
"path/filepath"
1212
"time"
1313

14-
"github.com/ahmetalpbalkan/go-cursor"
14+
cursor "github.com/ahmetalpbalkan/go-cursor"
1515
"github.com/fatih/color"
1616
"github.com/mholt/archiver"
1717
"github.com/pkg/errors"
18-
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
19-
"github.com/replicatedhq/troubleshoot/pkg/collect"
2018
"github.com/spf13/viper"
21-
"github.com/tj/go-spin"
19+
spin "github.com/tj/go-spin"
2220
"gopkg.in/yaml.v2"
21+
22+
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
23+
"github.com/replicatedhq/troubleshoot/pkg/collect"
2324
)
2425

2526
func runTroubleshoot(v *viper.Viper, arg string) error {
@@ -65,7 +66,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
6566

6667
s := spin.New()
6768
finishedCh := make(chan bool, 1)
68-
progressChan := make(chan interface{}, 1)
69+
progressChan := make(chan interface{}, 0) // non-zero buffer can result in missed messages
6970
go func() {
7071
currentDir := ""
7172
for {
@@ -91,7 +92,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
9192
}
9293
}()
9394
defer func() {
94-
finishedCh <- true
95+
close(finishedCh)
9596
}()
9697

9798
archivePath, err := runCollectors(v, collector, progressChan)
@@ -150,26 +151,48 @@ func runCollectors(v *viper.Viper, collector troubleshootv1beta1.Collector, prog
150151
return "", errors.Wrap(err, "write version file")
151152
}
152153

153-
desiredCollectors := make([]*troubleshootv1beta1.Collect, 0, 0)
154-
for _, definedCollector := range collector.Spec.Collectors {
155-
desiredCollectors = append(desiredCollectors, definedCollector)
156-
}
157-
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
158-
desiredCollectors = ensureCollectorInList(desiredCollectors, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
154+
collectSpecs := make([]*troubleshootv1beta1.Collect, 0, 0)
155+
collectSpecs = append(collectSpecs, collector.Spec.Collectors...)
156+
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterInfo: &troubleshootv1beta1.ClusterInfo{}})
157+
collectSpecs = ensureCollectorInList(collectSpecs, troubleshootv1beta1.Collect{ClusterResources: &troubleshootv1beta1.ClusterResources{}})
159158

160159
config, err := KubernetesConfigFlags.ToRESTConfig()
161160
if err != nil {
162161
return "", errors.Wrap(err, "failed to convert kube flags to rest config")
163162
}
164163

165-
// Run preflights collectors synchronously
166-
for _, desiredCollector := range desiredCollectors {
164+
var collectors collect.Collectors
165+
for _, desiredCollector := range collectSpecs {
167166
collector := collect.Collector{
168167
Redact: true,
169168
Collect: desiredCollector,
170169
ClientConfig: config,
171170
Namespace: v.GetString("namespace"),
172171
}
172+
collectors = append(collectors, &collector)
173+
}
174+
175+
if err := collectors.CheckRBAC(); err != nil {
176+
return "", errors.Wrap(err, "failed to check RBAC for collectors")
177+
}
178+
179+
foundForbidden := false
180+
for _, c := range collectors {
181+
for _, e := range c.RBACErrors {
182+
foundForbidden = true
183+
progressChan <- e
184+
}
185+
}
186+
187+
if foundForbidden && !v.GetBool("collect-without-permissions") {
188+
return "", errors.New("insufficient permissions to run all collectors")
189+
}
190+
191+
// Run preflights collectors synchronously
192+
for _, collector := range collectors {
193+
if len(collector.RBACErrors) > 0 {
194+
continue
195+
}
173196

174197
progressChan <- collector.GetDisplayName()
175198

@@ -338,3 +361,8 @@ func tarSupportBundleDir(inputDir, outputFilename string) error {
338361

339362
return nil
340363
}
364+
365+
type CollectorFailure struct {
366+
Collector *troubleshootv1beta1.Collect
367+
Failure string
368+
}

0 commit comments

Comments
 (0)