@@ -10,6 +10,7 @@ import (
1010 "os/signal"
1111 "path/filepath"
1212 "reflect"
13+ "strings"
1314 "sync"
1415 "time"
1516
@@ -27,6 +28,7 @@ import (
2728 "github.com/replicatedhq/troubleshoot/pkg/httputil"
2829 "github.com/replicatedhq/troubleshoot/pkg/k8sutil"
2930 "github.com/replicatedhq/troubleshoot/pkg/loader"
31+ "github.com/replicatedhq/troubleshoot/pkg/redact"
3032 "github.com/replicatedhq/troubleshoot/pkg/supportbundle"
3133 "github.com/replicatedhq/troubleshoot/pkg/types"
3234 "github.com/spf13/viper"
@@ -60,6 +62,10 @@ func runTroubleshoot(v *viper.Viper, args []string) error {
6062 return errors .Wrap (err , "invalid auto-discovery configuration" )
6163 }
6264
65+ // Validate tokenization flags
66+ if err := ValidateTokenizationFlags (v ); err != nil {
67+ return errors .Wrap (err , "invalid tokenization configuration" )
68+ }
6369 // Apply auto-discovery if enabled
6470 autoConfig := GetAutoDiscoveryConfig (v )
6571 if autoConfig .Enabled {
@@ -185,9 +191,9 @@ func runTroubleshoot(v *viper.Viper, args []string) error {
185191 }
186192 case <- time .After (time .Millisecond * 100 ):
187193 if currentDir == "" {
188- fmt .Printf ("\r %s \u001b [36mCollecting support bundle\u001b [m %s" , cursor .ClearEntireLine (), s .Next ())
194+ fmt .Printf ("\r %s \033 [36mCollecting support bundle\033 [m %s" , cursor .ClearEntireLine (), s .Next ())
189195 } else {
190- fmt .Printf ("\r %s \u001b [36mCollecting support bundle\u001b [m %s %s" , cursor .ClearEntireLine (), s .Next (), currentDir )
196+ fmt .Printf ("\r %s \033 [36mCollecting support bundle\033 [m %s %s" , cursor .ClearEntireLine (), s .Next (), currentDir )
191197 }
192198 }
193199 }
@@ -205,6 +211,15 @@ func runTroubleshoot(v *viper.Viper, args []string) error {
205211 Redact : v .GetBool ("redact" ),
206212 FromCLI : true ,
207213 RunHostCollectorsInPod : mainBundle .Spec .RunHostCollectorsInPod ,
214+
215+ // Phase 4: Tokenization options
216+ Tokenize : v .GetBool ("tokenize" ),
217+ RedactionMapPath : v .GetString ("redaction-map" ),
218+ EncryptRedactionMap : v .GetBool ("encrypt-redaction-map" ),
219+ TokenPrefix : v .GetString ("token-prefix" ),
220+ VerifyTokenization : v .GetBool ("verify-tokenization" ),
221+ BundleID : v .GetString ("bundle-id" ),
222+ TokenizationStats : v .GetBool ("tokenization-stats" ),
208223 }
209224
210225 nonInteractiveOutput := analysisOutput {}
@@ -217,18 +232,6 @@ func runTroubleshoot(v *viper.Viper, args []string) error {
217232 close (progressChan ) // this removes the spinner in interactive mode
218233 isProgressChanClosed = true
219234
220- // Auto-upload if requested
221- if v .GetBool ("auto-upload" ) {
222- licenseID := v .GetString ("license-id" )
223- appSlug := v .GetString ("app-slug" )
224-
225- fmt .Fprintf (os .Stderr , "Auto-uploading bundle to replicated.app...\n " )
226- if err := supportbundle .UploadBundleAutoDetect (response .ArchivePath , licenseID , appSlug ); err != nil {
227- fmt .Fprintf (os .Stderr , "Auto-upload failed: %v\n " , err )
228- fmt .Fprintf (os .Stderr , "You can manually upload the bundle using: support-bundle upload %s\n " , response .ArchivePath )
229- }
230- }
231-
232235 if len (response .AnalyzerResults ) > 0 {
233236 if interactive {
234237 if err := showInteractiveResults (mainBundle .Name , response .AnalyzerResults , response .ArchivePath ); err != nil {
@@ -498,3 +501,106 @@ func (a *analysisOutput) FormattedAnalysisOutput() (outputJson string, err error
498501 }
499502 return string (formatted ), nil
500503}
504+
505+ // ValidateTokenizationFlags validates tokenization flag combinations
506+ func ValidateTokenizationFlags (v * viper.Viper ) error {
507+ // Verify tokenization mode early (before collection starts)
508+ if v .GetBool ("verify-tokenization" ) {
509+ if err := VerifyTokenizationSetup (v ); err != nil {
510+ return errors .Wrap (err , "tokenization verification failed" )
511+ }
512+ fmt .Println ("✅ Tokenization verification passed" )
513+ os .Exit (0 ) // Exit after verification
514+ }
515+
516+ // Encryption requires redaction map
517+ if v .GetBool ("encrypt-redaction-map" ) && v .GetString ("redaction-map" ) == "" {
518+ return errors .New ("--encrypt-redaction-map requires --redaction-map to be specified" )
519+ }
520+
521+ // Redaction map requires tokenization or redaction to be enabled
522+ if v .GetString ("redaction-map" ) != "" {
523+ if ! v .GetBool ("tokenize" ) && ! v .GetBool ("redact" ) {
524+ return errors .New ("--redaction-map requires either --tokenize or --redact to be enabled" )
525+ }
526+ }
527+
528+ // Custom token prefix requires tokenization
529+ if v .GetString ("token-prefix" ) != "" && ! v .GetBool ("tokenize" ) {
530+ return errors .New ("--token-prefix requires --tokenize to be enabled" )
531+ }
532+
533+ // Bundle ID requires tokenization
534+ if v .GetString ("bundle-id" ) != "" && ! v .GetBool ("tokenize" ) {
535+ return errors .New ("--bundle-id requires --tokenize to be enabled" )
536+ }
537+
538+ // Tokenization stats requires tokenization
539+ if v .GetBool ("tokenization-stats" ) && ! v .GetBool ("tokenize" ) {
540+ return errors .New ("--tokenization-stats requires --tokenize to be enabled" )
541+ }
542+
543+ return nil
544+ }
545+
546+ // VerifyTokenizationSetup verifies tokenization configuration without collecting data
547+ func VerifyTokenizationSetup (v * viper.Viper ) error {
548+ fmt .Println ("🔍 Verifying tokenization setup..." )
549+
550+ // Test 1: Environment variable check
551+ if v .GetBool ("tokenize" ) {
552+ os .Setenv ("TROUBLESHOOT_TOKENIZATION" , "true" )
553+ defer os .Unsetenv ("TROUBLESHOOT_TOKENIZATION" )
554+ }
555+
556+ // Test 2: Tokenizer initialization
557+ redact .ResetGlobalTokenizer ()
558+ tokenizer := redact .GetGlobalTokenizer ()
559+
560+ if v .GetBool ("tokenize" ) && ! tokenizer .IsEnabled () {
561+ return errors .New ("tokenizer is not enabled despite --tokenize flag" )
562+ }
563+
564+ if ! v .GetBool ("tokenize" ) && tokenizer .IsEnabled () {
565+ return errors .New ("tokenizer is enabled despite --tokenize flag being false" )
566+ }
567+
568+ fmt .Printf (" ✅ Tokenizer state: %v\n " , tokenizer .IsEnabled ())
569+
570+ // Test 3: Token generation
571+ if tokenizer .IsEnabled () {
572+ testToken := tokenizer .TokenizeValue ("test-secret" , "verification" )
573+ if ! tokenizer .ValidateToken (testToken ) {
574+ return errors .Errorf ("generated test token is invalid: %s" , testToken )
575+ }
576+ fmt .Printf (" ✅ Test token generated: %s\n " , testToken )
577+ }
578+
579+ // Test 4: Custom token prefix validation
580+ if customPrefix := v .GetString ("token-prefix" ); customPrefix != "" {
581+ if ! strings .Contains (customPrefix , "%s" ) {
582+ return errors .Errorf ("custom token prefix must contain %%s placeholders: %s" , customPrefix )
583+ }
584+ fmt .Printf (" ✅ Custom token prefix validated: %s\n " , customPrefix )
585+ }
586+
587+ // Test 5: Redaction map path validation
588+ if mapPath := v .GetString ("redaction-map" ); mapPath != "" {
589+ // Check if directory exists
590+ dir := filepath .Dir (mapPath )
591+ if _ , err := os .Stat (dir ); os .IsNotExist (err ) {
592+ return errors .Errorf ("redaction map directory does not exist: %s" , dir )
593+ }
594+ fmt .Printf (" ✅ Redaction map path validated: %s\n " , mapPath )
595+
596+ // Test file creation (and cleanup)
597+ testFile := mapPath + ".test"
598+ if err := os .WriteFile (testFile , []byte ("test" ), 0600 ); err != nil {
599+ return errors .Errorf ("cannot create redaction map file: %v" , err )
600+ }
601+ os .Remove (testFile )
602+ fmt .Printf (" ✅ File creation permissions verified\n " )
603+ }
604+
605+ return nil
606+ }
0 commit comments