Skip to content

Commit db5ed0f

Browse files
authored
Merge pull request #450 from gyanranjanpanda/fix/redact-oauth-tokens-from-logs
fix: remove OAuth token logging and redact sensitive data from CLI output
2 parents d63b649 + 0f8b000 commit db5ed0f

4 files changed

Lines changed: 121 additions & 25 deletions

File tree

cmd/login.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ func oauth2login(
219219
// Authorization redirect callback from OAuth2 auth flow.
220220
// Handles both implicit and authorization code flow
221221
callbackHandler := func(w http.ResponseWriter, r *http.Request) {
222-
log.Printf("Callback: %s\n", r.URL)
222+
log.Printf("Callback received on: %s\n", r.URL.Path)
223223

224224
if formErr := r.FormValue("error"); formErr != "" {
225225
handleErr(w, fmt.Sprintf("%s: %s", formErr, r.FormValue("error_description")))
@@ -276,7 +276,8 @@ func oauth2login(
276276
opts = append(opts, oauth2.SetAuthURLParam("code_challenge_method", "S256"))
277277
url = oauth2conf.AuthCodeURL(stateNonce, opts...)
278278

279-
fmt.Printf("Performing %s flow login: %s\n", "authorization_code", url)
279+
authBaseURL := strings.SplitN(url, "?", 2)[0]
280+
fmt.Printf("Performing %s flow login: %s\n", "authorization_code", authBaseURL)
280281
time.Sleep(1 * time.Second)
281282
ssoAuthFlow(url, ssoLaunchBrowser)
282283
go func() {
@@ -293,6 +294,7 @@ func oauth2login(
293294
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
294295
defer cancel()
295296
_ = srv.Shutdown(ctx)
297+
296298
return tokenString, refreshToken
297299
}
298300

cmd/logout.go

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77

88
"github.com/microcks/microcks-cli/pkg/config"
99
"github.com/microcks/microcks-cli/pkg/connectors"
10-
"github.com/microcks/microcks-cli/pkg/errors"
1110
"github.com/spf13/cobra"
1211
)
1312

@@ -29,29 +28,40 @@ microcks logout dev-context`,
2928
os.Exit(1)
3029
}
3130

32-
context := args[0]
33-
localCfg, err := config.ReadLocalConfig(globalClientOpts.ConfigPath)
34-
errors.CheckError(err)
35-
if localCfg == nil {
36-
log.Fatalf("Nothing to logout from")
37-
}
38-
39-
// Remove authToken
40-
ok := localCfg.RemoveToken(context)
41-
if !ok {
42-
log.Fatalf("Context %s does not exist", context)
43-
}
44-
45-
err = config.ValidateLocalConfig(*localCfg)
31+
target := args[0]
32+
err := logoutContext(target, globalClientOpts.ConfigPath)
4633
if err != nil {
47-
log.Fatalf("Error in loging out: %s", err)
34+
log.Fatal(err)
4835
}
49-
err = config.WriteLocalConfig(*localCfg, globalClientOpts.ConfigPath)
50-
errors.CheckError(err)
51-
52-
fmt.Printf("Logged out from '%s'\n", context)
36+
fmt.Printf("Logged out from '%s'\n", target)
5337
},
5438
}
5539

5640
return logoutCmd
5741
}
42+
43+
func logoutContext(target, configPath string) error {
44+
localCfg, err := config.ReadLocalConfig(configPath)
45+
if err != nil {
46+
return err
47+
}
48+
if localCfg == nil {
49+
return fmt.Errorf("Nothing to logout from")
50+
}
51+
52+
userName := target
53+
if ctx, err := localCfg.ResolveContext(target); err == nil {
54+
userName = ctx.User.Name
55+
}
56+
57+
if ok := localCfg.RemoveToken(userName); !ok {
58+
return fmt.Errorf("Context %s does not exist", target)
59+
}
60+
61+
err = config.ValidateLocalConfig(*localCfg)
62+
if err != nil {
63+
return fmt.Errorf("Error in loging out: %s", err)
64+
}
65+
66+
return config.WriteLocalConfig(*localCfg, configPath)
67+
}

cmd/logout_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package cmd
2+
3+
import (
4+
"path/filepath"
5+
"testing"
6+
7+
"github.com/microcks/microcks-cli/pkg/config"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestLogoutContextResolvesNamedContextUser(t *testing.T) {
12+
configPath := filepath.Join(t.TempDir(), "config")
13+
server := "https://microcks.example"
14+
15+
localCfg := config.LocalConfig{
16+
CurrentContext: "staging",
17+
Contexts: []config.ContextRef{
18+
{Name: "staging", Server: server, User: server},
19+
},
20+
Servers: []config.Server{
21+
{Server: server, KeycloakEnable: true},
22+
},
23+
Users: []config.User{
24+
{Name: server, AuthToken: "access-token", RefreshToken: "refresh-token"},
25+
},
26+
}
27+
require.NoError(t, config.WriteLocalConfig(localCfg, configPath))
28+
29+
require.NoError(t, logoutContext("staging", configPath))
30+
31+
updated, err := config.ReadLocalConfig(configPath)
32+
require.NoError(t, err)
33+
require.NotNil(t, updated)
34+
35+
user, err := updated.GetUser(server)
36+
require.NoError(t, err)
37+
require.Empty(t, user.AuthToken)
38+
require.Empty(t, user.RefreshToken)
39+
}
40+
41+
func TestLogoutContextStillAcceptsStoredUserName(t *testing.T) {
42+
configPath := filepath.Join(t.TempDir(), "config")
43+
server := "https://microcks.example"
44+
45+
localCfg := config.LocalConfig{
46+
CurrentContext: "staging",
47+
Contexts: []config.ContextRef{
48+
{Name: "staging", Server: server, User: server},
49+
},
50+
Servers: []config.Server{
51+
{Server: server, KeycloakEnable: true},
52+
},
53+
Users: []config.User{
54+
{Name: server, AuthToken: "access-token", RefreshToken: "refresh-token"},
55+
},
56+
}
57+
require.NoError(t, config.WriteLocalConfig(localCfg, configPath))
58+
59+
require.NoError(t, logoutContext(server, configPath))
60+
61+
updated, err := config.ReadLocalConfig(configPath)
62+
require.NoError(t, err)
63+
require.NotNil(t, updated)
64+
65+
user, err := updated.GetUser(server)
66+
require.NoError(t, err)
67+
require.Empty(t, user.AuthToken)
68+
require.Empty(t, user.RefreshToken)
69+
}

pkg/config/config.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import (
2323
"net/http/httputil"
2424
"os"
2525
"path/filepath"
26-
strings "strings"
26+
"regexp"
27+
"strings"
2728
)
2829

2930
var (
@@ -37,6 +38,13 @@ var (
3738
ConfigPath = filepath.Join(os.Getenv("HOME"), ".microcks-cli", "config.yaml")
3839
)
3940

41+
var sensitiveHeaderPattern = regexp.MustCompile(
42+
`(?im)^(Authorization:\s*)(Bearer\s+)?(.+)$`,
43+
)
44+
var sensitiveParamPattern = regexp.MustCompile(
45+
`(?i)(access_token|refresh_token|id_token|code)=([^&\s]+)`,
46+
)
47+
4048
// CreateTLSConfig wraps the creation of tls.Config object for use with HTTP Client for example.
4149
func CreateTLSConfig() *tls.Config {
4250
tlsConfig := &tls.Config{}
@@ -76,7 +84,7 @@ func DumpRequestIfRequired(name string, req *http.Request, body bool) {
7684
if err != nil {
7785
fmt.Println("Got error while dumping request out")
7886
}
79-
fmt.Printf("%s", dump)
87+
fmt.Printf("%s", redactSensitiveContent(string(dump)))
8088
}
8189
}
8290

@@ -88,9 +96,16 @@ func DumpResponseIfRequired(name string, resp *http.Response, body bool) {
8896
if err != nil {
8997
fmt.Println("Got error while dumping response")
9098
}
91-
fmt.Printf("%s", dump)
99+
fmt.Printf("%s", redactSensitiveContent(string(dump)))
92100
if body {
93101
fmt.Println("")
94102
}
95103
}
96104
}
105+
106+
// redactSensitiveContent masks OAuth tokens and credentials in HTTP dump output.
107+
func redactSensitiveContent(dump string) string {
108+
redacted := sensitiveHeaderPattern.ReplaceAllString(dump, "${1}[REDACTED]")
109+
redacted = sensitiveParamPattern.ReplaceAllString(redacted, "${1}=[REDACTED]")
110+
return redacted
111+
}

0 commit comments

Comments
 (0)