Skip to content

Commit 9dd5ad4

Browse files
feat: Redact token information from debug logs [HEAD-1184] (#4983)
* test: add failing token redact test * feat: adopt log scrubbing * fix: debuglog test * use common func to get scrubdict * use scrub logger in Extensible CLI * chore: run formatter * fix: acceptance test through gaf upgrade * refactor: cleaning up logger initialization * fix: toolchain * refactor: move debug logic in separate file to reduce main * fix: init order and redact tests * chore: run formatter * chore: use final gaf commit hash * chore: undo unneccessary change * fix: add missing fips_enable import --------- Co-authored-by: Luke Watts <[email protected]>
1 parent 0226e20 commit 9dd5ad4

File tree

7 files changed

+135
-61
lines changed

7 files changed

+135
-61
lines changed

cliv2/cmd/cliv2/debug.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package main
2+
3+
// !!! This import needs to be the first import, please do not change this !!!
4+
import _ "github.com/snyk/go-application-framework/pkg/networking/fips_enable"
5+
6+
import (
7+
"fmt"
8+
"os"
9+
"strings"
10+
"time"
11+
12+
"github.com/rs/zerolog"
13+
"github.com/snyk/go-application-framework/pkg/configuration"
14+
"github.com/snyk/go-application-framework/pkg/logging"
15+
)
16+
17+
func getDebugLevel(config configuration.Configuration, logger *zerolog.Logger) zerolog.Level {
18+
loglevel := zerolog.DebugLevel
19+
if loglevelString := config.GetString("snyk_log_level"); loglevelString != "" {
20+
var err error
21+
loglevel, err = zerolog.ParseLevel(loglevelString)
22+
if err == nil {
23+
logger.Log().Msgf("Setting log level to %s", loglevelString)
24+
} else {
25+
logger.Log().Msgf("%v", err)
26+
loglevel = zerolog.DebugLevel
27+
}
28+
}
29+
return loglevel
30+
}
31+
32+
func initDebugLogger(config configuration.Configuration) *zerolog.Logger {
33+
debug := config.GetBool(configuration.DEBUG)
34+
if !debug {
35+
return &noopLogger
36+
} else {
37+
var consoleWriter = zerolog.ConsoleWriter{
38+
Out: os.Stderr,
39+
TimeFormat: time.RFC3339,
40+
NoColor: true,
41+
PartsOrder: []string{
42+
zerolog.TimestampFieldName,
43+
"ext",
44+
"separator",
45+
zerolog.CallerFieldName,
46+
zerolog.MessageFieldName,
47+
},
48+
FieldsExclude: []string{"ext", "separator"},
49+
FormatTimestamp: func(i interface{}) string {
50+
t, _ := time.Parse(time.RFC3339, i.(string))
51+
return strings.ToUpper(fmt.Sprintf("%s", t.UTC().Format(time.RFC3339)))
52+
},
53+
}
54+
55+
scrubLogger := logging.NewScrubbingWriter(zerolog.MultiLevelWriter(consoleWriter), logging.GetScrubDictFromConfig(config))
56+
localLogger := zerolog.New(scrubLogger).With().Str("ext", "main").Str("separator", "-").Timestamp().Logger()
57+
loglevel := getDebugLevel(config, &localLogger)
58+
debugLogger := localLogger.Level(loglevel)
59+
return &debugLogger
60+
}
61+
}

cliv2/cmd/cliv2/logheader.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func logHeaderAuthorizationInfo(
3636

3737
err := networkAccess.AddHeaders(apiRequest)
3838
if err != nil {
39-
debugLogger.Print(err)
39+
globalLogger.Print(err)
4040
}
4141

4242
authHeader := apiRequest.Header.Get("Authorization")
@@ -97,7 +97,7 @@ func writeLogHeader(config configuration.Configuration, networkAccess networking
9797
}
9898

9999
tablePrint := func(name string, value string) {
100-
debugLogger.Printf("%-22s %s", name+":", value)
100+
globalLogger.Printf("%-22s %s", name+":", value)
101101
}
102102

103103
fipsEnabled := getFipsStatus(config)

cliv2/cmd/cliv2/main.go

Lines changed: 18 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,9 @@ var internalOS string
4444
var engine workflow.Engine
4545
var globalConfiguration configuration.Configuration
4646
var helpProvided bool
47-
var debugLogger = zerolog.New(zerolog.ConsoleWriter{
48-
Out: os.Stderr,
49-
TimeFormat: time.RFC3339,
50-
NoColor: true,
51-
PartsOrder: []string{
52-
zerolog.TimestampFieldName,
53-
"ext",
54-
"separator",
55-
zerolog.CallerFieldName,
56-
zerolog.MessageFieldName,
57-
},
58-
FieldsExclude: []string{"ext", "separator"},
59-
FormatTimestamp: func(i interface{}) string {
60-
t, _ := time.Parse(time.RFC3339, i.(string))
61-
return strings.ToUpper(fmt.Sprintf("%s", t.UTC().Format(time.RFC3339)))
62-
},
63-
}).With().Str("ext", "main").Str("separator", "-").Timestamp().Logger()
47+
48+
var noopLogger zerolog.Logger = zerolog.New(io.Discard)
49+
var globalLogger *zerolog.Logger = &noopLogger
6450

6551
const (
6652
unknownCommandMessage string = "unknown command"
@@ -81,32 +67,6 @@ const (
8167
handleErrorUnhandled HandleError = iota
8268
)
8369

84-
func getDebugLevel(config configuration.Configuration) zerolog.Level {
85-
loglevel := zerolog.DebugLevel
86-
if loglevelString := config.GetString("snyk_log_level"); loglevelString != "" {
87-
var err error
88-
loglevel, err = zerolog.ParseLevel(loglevelString)
89-
if err == nil {
90-
debugLogger.Log().Msgf("Setting log level to %s", loglevelString)
91-
} else {
92-
debugLogger.Log().Msgf("%v", err)
93-
loglevel = zerolog.DebugLevel
94-
}
95-
}
96-
return loglevel
97-
}
98-
99-
func initDebugLogger(config configuration.Configuration) *zerolog.Logger {
100-
debug := config.GetBool(configuration.DEBUG)
101-
if !debug {
102-
debugLogger = debugLogger.Output(io.Discard)
103-
} else {
104-
loglevel := getDebugLevel(config)
105-
debugLogger = debugLogger.Level(loglevel)
106-
}
107-
return &debugLogger
108-
}
109-
11070
func main() {
11171
errorCode := MainWithErrorCode()
11272
os.Exit(errorCode)
@@ -132,7 +92,7 @@ func initApplicationConfiguration(config configuration.Configuration) {
13292
formattedKey := strings.ToUpper(key)
13393
_, ok := os.LookupEnv(formattedKey)
13494
if ok {
135-
debugLogger.Printf("Found environment variable %s, disabling OAuth flow", formattedKey)
95+
globalLogger.Printf("Found environment variable %s, disabling OAuth flow", formattedKey)
13696
config.Set(configuration.FF_OAUTH_AUTH_FLOW_ENABLED, false)
13797
break
13898
}
@@ -187,21 +147,21 @@ func runMainWorkflow(config configuration.Configuration, cmd *cobra.Command, arg
187147

188148
err := config.AddFlagSet(cmd.Flags())
189149
if err != nil {
190-
debugLogger.Print("Failed to add flags", err)
150+
globalLogger.Print("Failed to add flags", err)
191151
return err
192152
}
193153

194154
updateConfigFromParameter(config, args, rawArgs)
195155

196156
name := getFullCommandString(cmd)
197-
debugLogger.Print("Running ", name)
157+
globalLogger.Print("Running ", name)
198158
engine.GetAnalytics().SetCommand(name)
199159

200160
data, err := engine.Invoke(workflow.NewWorkflowIdentifier(name))
201161
if err == nil {
202162
_, err = engine.InvokeWithInput(localworkflows.WORKFLOWID_OUTPUT_WORKFLOW, data)
203163
} else {
204-
debugLogger.Print("Failed to execute the command!", err)
164+
globalLogger.Print("Failed to execute the command!", err)
205165
}
206166

207167
return err
@@ -391,14 +351,16 @@ func MainWithErrorCode() int {
391351
globalConfiguration = configuration.New()
392352
err = globalConfiguration.AddFlagSet(rootCommand.LocalFlags())
393353
if err != nil {
394-
debugLogger.Print("Failed to add flags to root command", err)
354+
fmt.Fprintln(os.Stderr, "Failed to add flags to root command", err)
395355
}
396356

357+
// ensure to init configuration before using it
358+
initApplicationConfiguration(globalConfiguration)
359+
397360
debugEnabled := globalConfiguration.GetBool(configuration.DEBUG)
398-
debugLogger := initDebugLogger(globalConfiguration)
361+
globalLogger = initDebugLogger(globalConfiguration)
399362

400-
initApplicationConfiguration(globalConfiguration)
401-
engine = app.CreateAppEngineWithOptions(app.WithZeroLogger(debugLogger), app.WithConfiguration(globalConfiguration), app.WithRuntimeInfo(rInfo))
363+
engine = app.CreateAppEngineWithOptions(app.WithZeroLogger(globalLogger), app.WithConfiguration(globalConfiguration), app.WithRuntimeInfo(rInfo))
402364

403365
if noProxyAuth := globalConfiguration.GetBool(basic_workflows.PROXY_NOAUTH); noProxyAuth {
404366
globalConfiguration.Set(configuration.PROXY_AUTHENTICATION_MECHANISM, httpauth.StringFromAuthenticationMechanism(httpauth.NoAuth))
@@ -416,7 +378,7 @@ func MainWithErrorCode() int {
416378
// init engine
417379
err = engine.Init()
418380
if err != nil {
419-
debugLogger.Print("Failed to init Workflow Engine!", err)
381+
globalLogger.Print("Failed to init Workflow Engine!", err)
420382
return constants.SNYK_EXIT_CODE_ERROR
421383
}
422384

@@ -449,7 +411,7 @@ func MainWithErrorCode() int {
449411
cliAnalytics.SetCmdArguments(os.Args[1:])
450412
cliAnalytics.SetOperatingSystem(internalOS)
451413
if globalConfiguration.GetBool(configuration.ANALYTICS_DISABLED) == false {
452-
defer sendAnalytics(cliAnalytics, debugLogger)
414+
defer sendAnalytics(cliAnalytics, globalLogger)
453415
}
454416

455417
setTimeout(globalConfiguration, func() {
@@ -462,7 +424,7 @@ func MainWithErrorCode() int {
462424
// fallback to the legacy cli or show help
463425
handleErrorResult := handleError(err)
464426
if handleErrorResult == handleErrorFallbackToLegacyCLI {
465-
debugLogger.Printf("Using Legacy CLI to serve the command. (reason: %v)", err)
427+
globalLogger.Printf("Using Legacy CLI to serve the command. (reason: %v)", err)
466428
err = defaultCmd(os.Args[1:])
467429
} else if handleErrorResult == handleErrorShowHelp {
468430
err = help(nil, []string{})
@@ -475,7 +437,7 @@ func MainWithErrorCode() int {
475437
displayError(err)
476438

477439
exitCode := cliv2.DeriveExitCode(err)
478-
debugLogger.Printf("Exiting with %d", exitCode)
440+
globalLogger.Printf("Exiting with %d", exitCode)
479441

480442
return exitCode
481443
}
@@ -485,7 +447,7 @@ func setTimeout(config configuration.Configuration, onTimeout func()) {
485447
if timeout == 0 {
486448
return
487449
}
488-
debugLogger.Printf("Command timeout set for %d seconds", timeout)
450+
globalLogger.Printf("Command timeout set for %d seconds", timeout)
489451
go func() {
490452
const gracePeriodForSubProcesses = 3
491453
<-time.After(time.Duration(timeout+gracePeriodForSubProcesses) * time.Second)

cliv2/go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ require (
1313
github.com/snyk/cli-extension-iac-rules v0.0.0-20230601153200-c572cfce46ce
1414
github.com/snyk/cli-extension-sbom v0.0.0-20231123083311-52b1cecc1a7a
1515
github.com/snyk/container-cli v0.0.0-20230920093251-fe865879a91f
16-
github.com/snyk/go-application-framework v0.0.0-20231222162659-c767e4a7440b
16+
github.com/snyk/go-application-framework v0.0.0-20240105122614-54e2b7c15259
1717
github.com/snyk/go-httpauth v0.0.0-20231117135515-eb445fea7530
1818
github.com/snyk/snyk-iac-capture v0.6.5
1919
github.com/snyk/snyk-ls v0.0.0-20231124091213-5a223c21e0aa
@@ -173,3 +173,5 @@ require (
173173

174174
// version 2491eb6c1c75 contains a valid license
175175
replace github.com/mattn/go-localereader v0.0.1 => github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75
176+
177+
//replace github.com/snyk/go-application-framework => ../../go-application-framework

cliv2/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -665,8 +665,8 @@ github.com/snyk/cli-extension-sbom v0.0.0-20231123083311-52b1cecc1a7a h1:oRrk9bv
665665
github.com/snyk/cli-extension-sbom v0.0.0-20231123083311-52b1cecc1a7a/go.mod h1:IwRGWjRuNkY08O7NJb7u3JuQkroEB8Qi1MlASpZVu1Q=
666666
github.com/snyk/container-cli v0.0.0-20230920093251-fe865879a91f h1:ghajT5PEiLP8XNFIdc7Yn4Th74RH/9Q++dDOp6Cb9eo=
667667
github.com/snyk/container-cli v0.0.0-20230920093251-fe865879a91f/go.mod h1:38w+dcAQp9eG3P5t2eNS9eG0reut10AeJjLv5lJ5lpM=
668-
github.com/snyk/go-application-framework v0.0.0-20231222162659-c767e4a7440b h1:NNiXGaKELaFmejlw5BOWf8dVThl8iisU9Yhx+FSUrL4=
669-
github.com/snyk/go-application-framework v0.0.0-20231222162659-c767e4a7440b/go.mod h1:Yz/qxFyfhf0xbA+z8Vzr5IM9IDG+BS+2PiGaP1yAsEw=
668+
github.com/snyk/go-application-framework v0.0.0-20240105122614-54e2b7c15259 h1:u6CV0KCHuqPINEs83CbVCjsxG5wMxa42T5HtMvKgm+o=
669+
github.com/snyk/go-application-framework v0.0.0-20240105122614-54e2b7c15259/go.mod h1:Yz/qxFyfhf0xbA+z8Vzr5IM9IDG+BS+2PiGaP1yAsEw=
670670
github.com/snyk/go-httpauth v0.0.0-20231117135515-eb445fea7530 h1:s9PHNkL6ueYRiAKNfd8OVxlUOqU3qY0VDbgCD1f6WQY=
671671
github.com/snyk/go-httpauth v0.0.0-20231117135515-eb445fea7530/go.mod h1:88KbbvGYlmLgee4OcQ19yr0bNpXpOr2kciOthaSzCAg=
672672
github.com/snyk/policy-engine v0.22.0 h1:od9pduGrXyfWO791X+8M1qmnvWUxaIXh0gBzGKqeseA=

cliv2/pkg/basic_workflows/legacycli.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/pkg/errors"
1010
"github.com/snyk/go-application-framework/pkg/auth"
1111
"github.com/snyk/go-application-framework/pkg/configuration"
12+
"github.com/snyk/go-application-framework/pkg/logging"
1213
pkg_utils "github.com/snyk/go-application-framework/pkg/utils"
1314
"github.com/snyk/go-application-framework/pkg/workflow"
1415
"github.com/snyk/go-httpauth/pkg/httpauth"
@@ -124,6 +125,10 @@ func legacycliWorkflow(
124125
outWriter = bufio.NewWriter(&outBuffer)
125126
errWriter = bufio.NewWriter(&errBuffer)
126127
cli.SetIoStreams(in, outWriter, errWriter)
128+
} else {
129+
scrubDict := logging.GetScrubDictFromConfig(config)
130+
scrubbedStderr := logging.NewScrubbingIoWriter(os.Stderr, scrubDict)
131+
cli.SetIoStreams(os.Stdin, os.Stdout, scrubbedStderr)
127132
}
128133

129134
// init proxy object

test/jest/acceptance/debuglog.spec.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { runSnykCLI } from '../util/runSnykCLI';
2+
import { createProjectFromWorkspace } from '../util/createProject';
3+
4+
jest.setTimeout(1000 * 60);
5+
6+
describe('debug log', () => {
7+
it('redacts token from env var', async () => {
8+
const project = await createProjectFromWorkspace('cocoapods-app');
9+
const token = 'mytoken';
10+
11+
const { stderr } = await runSnykCLI('test -d', {
12+
cwd: project.path(),
13+
env: {
14+
...process.env,
15+
SNYK_DISABLE_ANALYTICS: '1',
16+
DEBUG: '*',
17+
SNYK_LOG_LEVEL: 'trace',
18+
SNYK_TOKEN: token,
19+
},
20+
});
21+
22+
expect(stderr).not.toContain(token);
23+
});
24+
25+
it('redacts token from config file', async () => {
26+
const project = await createProjectFromWorkspace('cocoapods-app');
27+
28+
const config = await runSnykCLI('config get api');
29+
const expectedToken = config.stdout.trim();
30+
31+
const { stderr } = await runSnykCLI('test -d', {
32+
cwd: project.path(),
33+
env: {
34+
...process.env,
35+
SNYK_DISABLE_ANALYTICS: '1',
36+
DEBUG: '*',
37+
SNYK_LOG_LEVEL: 'trace',
38+
},
39+
});
40+
41+
expect(expectedToken).not.toBeFalsy();
42+
expect(stderr).not.toContain(expectedToken);
43+
});
44+
});

0 commit comments

Comments
 (0)