diff --git a/pkg/c8ysession/c8ysession.go b/pkg/c8ysession/c8ysession.go index 611935f72..88e6f9cb4 100644 --- a/pkg/c8ysession/c8ysession.go +++ b/pkg/c8ysession/c8ysession.go @@ -117,25 +117,37 @@ func PrintSessionInfo(w io.Writer, client *c8y.Client, cfg *config.Config, sessi label := labelS.SprintfFunc() value := color.New(color.FgWhite).SprintFunc() header := color.New(color.FgCyan).SprintFunc() + maybeHideMessage := func(client *c8y.Client, message string) string { + return message + } + hideInfo := cfg.HideSessionBanner() + if hideInfo { + maybeHideMessage = cfg.HideSensitiveInformation + } - labelS.Fprintf(w, "--------------------- Cumulocity Session ---------------------\n") + if hideInfo { + labelS.Fprintf(w, "--------------------- Cumulocity Session (sensitive info is hidden) ---------------------\n") + } else { + labelS.Fprintf(w, "--------------------- Cumulocity Session ---------------------\n") + } + // Always show the source of the session if session.SessionUri != "" { - fmt.Fprintf(w, "\n %s: %s\n\n\n", label("%s", "source"), header(cfg.HideSensitiveInformationIfActive(client, session.SessionUri))) + fmt.Fprintf(w, "\n %s: %s\n\n\n", label("%s", "source"), header(session.SessionUri)) } else { - fmt.Fprintf(w, "\n %s: %s\n\n\n", label("%s", "path"), header(cfg.HideSensitiveInformationIfActive(client, session.Path))) + fmt.Fprintf(w, "\n %s: %s\n\n\n", label("%s", "path"), header(session.Path)) } if session.Description != "" { - fmt.Fprintf(w, "%s : %s\n", label(fmt.Sprintf("%-12s", "description")), value(cfg.HideSensitiveInformationIfActive(client, session.Host))) + fmt.Fprintf(w, "%s : %s\n", label(fmt.Sprintf("%-12s", "description")), value(maybeHideMessage(client, session.Host))) } - fmt.Fprintf(w, "%s : %s\n", label(fmt.Sprintf("%-12s", "host")), value(cfg.HideSensitiveInformationIfActive(client, session.Host))) + fmt.Fprintf(w, "%s : %s\n", label(fmt.Sprintf("%-12s", "host")), value(maybeHideMessage(client, session.Host))) if session.Tenant != "" { - fmt.Fprintf(w, "%s : %s\n", label(fmt.Sprintf("%-12s", "tenant")), value(cfg.HideSensitiveInformationIfActive(client, session.Tenant))) + fmt.Fprintf(w, "%s : %s\n", label(fmt.Sprintf("%-12s", "tenant")), value(maybeHideMessage(client, session.Tenant))) } if session.Version != "" { - fmt.Fprintf(w, "%s : %s\n", label(fmt.Sprintf("%-12s", "version")), value(cfg.HideSensitiveInformationIfActive(client, session.Version))) + fmt.Fprintf(w, "%s : %s\n", label(fmt.Sprintf("%-12s", "version")), value(maybeHideMessage(client, session.Version))) } - fmt.Fprintf(w, "%s : %s\n", label(fmt.Sprintf("%-12s", "username")), value(cfg.HideSensitiveInformationIfActive(client, session.Username))) + fmt.Fprintf(w, "%s : %s\n", label(fmt.Sprintf("%-12s", "username")), value(maybeHideMessage(client, session.Username))) fmt.Fprintf(w, "\n") } diff --git a/pkg/cmd/sessions/login/login.manual.go b/pkg/cmd/sessions/login/login.manual.go index 5c3f83c4f..b94eff025 100644 --- a/pkg/cmd/sessions/login/login.manual.go +++ b/pkg/cmd/sessions/login/login.manual.go @@ -251,7 +251,11 @@ func (n *CmdLogin) FromFile(file string, format string) (*c8ysession.CumulocityS return nil, err } - return n.FromViper(v) + session, err := n.FromViper(v) + if session.SessionUri == "" { + session.SessionUri = "file://" + file + } + return session, err } func (n *CmdLogin) FromReader(r io.Reader, format string) (*c8ysession.CumulocitySession, error) { @@ -366,6 +370,8 @@ func (n *CmdLogin) RunE(cmd *cobra.Command, args []string) error { session.Path = cfg.GetSessionFile() // Write session details to stderr (for humans) + cs := n.factory.IOStreams.ColorScheme() + fmt.Fprintf(n.factory.IOStreams.ErrOut, "%s Session is now active\n", cs.SuccessIcon()) if !n.NoBanner { c8ysession.PrintSessionInfo(n.SubCommand.GetCommand().ErrOrStderr(), client, cfg, *session) } diff --git a/pkg/cmd/sessions/selectsession/selectsession.go b/pkg/cmd/sessions/selectsession/selectsession.go index 12b971b99..db52ebc8b 100644 --- a/pkg/cmd/sessions/selectsession/selectsession.go +++ b/pkg/cmd/sessions/selectsession/selectsession.go @@ -122,7 +122,7 @@ func SelectSession(io *iostreams.IOStreams, cfg *config.Config, log *logger.Logg funcMap["highlight"] = customStyle() funcMap["hide"] = func(v interface{}) string { - if cfg.HideSensitive() { + if cfg.HideSessionBanner() { return "*****" } return fmt.Sprintf("%v", v) @@ -131,7 +131,7 @@ func SelectSession(io *iostreams.IOStreams, cfg *config.Config, log *logger.Logg funcMap["hideUser"] = func(v interface{}) string { msg := fmt.Sprintf("%v", v) - if !cfg.HideSensitive() { + if !cfg.HideSessionBanner() { return msg } if os.Getenv("USERNAME") != "" { diff --git a/pkg/cmd/sessions/set/set.manual.go b/pkg/cmd/sessions/set/set.manual.go index ad5ce41c8..ae7d676d3 100644 --- a/pkg/cmd/sessions/set/set.manual.go +++ b/pkg/cmd/sessions/set/set.manual.go @@ -1,7 +1,9 @@ package login import ( + "fmt" "os" + "slices" "strings" "time" @@ -28,6 +30,7 @@ type CmdSet struct { LoginType string Shell string ClearToken bool + NoBanner bool sessionFilter string *subcommand.SubCommand @@ -68,6 +71,7 @@ func NewCmdSet(f *cmdutil.Factory) *CmdSet { cmd.Flags().StringVar(&ccmd.TFACode, "tfaCode", "", "Two Factor Authentication code") cmd.Flags().StringVar(&ccmd.Shell, "shell", defaultShell, "Shell type to return the environment variables") cmd.Flags().StringVar(&ccmd.LoginType, "loginType", "", "Login type preference, e.g. OAUTH2_INTERNAL or BASIC. When set to BASIC, any existing token will be cleared") + cmd.Flags().BoolVar(&ccmd.NoBanner, "no-banner", false, "Don't show the session banner") cmd.Flags().BoolVar(&ccmd.ClearToken, "clear", false, "Clear any existing tokens") completion.WithOptions( @@ -120,12 +124,15 @@ func (n *CmdSet) RunE(cmd *cobra.Command, args []string) error { // the user is most likely switching session so does not want to inherit any environment variables // set from the last instance. // But this has a side effect that you can't control the profile handing via environment variables when using the interact session selection + allowedEnvValues := []string{"C8Y_SETTINGS_SESSION_HIDE"} env_prefix := strings.ToUpper(config.EnvSettingsPrefix) for _, env := range os.Environ() { if strings.HasPrefix(env, env_prefix) && !strings.HasPrefix(env, config.EnvPassphrase) && !strings.HasPrefix(env, config.EnvSessionHome) { parts := strings.SplitN(env, "=", 2) if len(parts) == 2 { - os.Unsetenv(parts[0]) + if !slices.Contains(allowedEnvValues, parts[0]) { + os.Unsetenv(parts[0]) + } } } } @@ -233,7 +240,11 @@ func (n *CmdSet) RunE(cmd *cobra.Command, args []string) error { // Write session details to stderr (for humans) if outputFormat != config.OutputJSON.String() { - c8ysession.PrintSessionInfo(n.SubCommand.GetCommand().ErrOrStderr(), client, cfg, *session) + cs := n.factory.IOStreams.ColorScheme() + fmt.Fprintf(n.factory.IOStreams.ErrOut, "%s Session is now active\n", cs.SuccessIcon()) + if !n.NoBanner { + c8ysession.PrintSessionInfo(n.factory.IOStreams.ErrOut, client, cfg, *session) + } } if outputFormat == config.OutputUnknown.String() { diff --git a/pkg/cmd/settings/update/update.manual.go b/pkg/cmd/settings/update/update.manual.go index 980974da3..dba862fe4 100644 --- a/pkg/cmd/settings/update/update.manual.go +++ b/pkg/cmd/settings/update/update.manual.go @@ -316,6 +316,12 @@ var updateSettingsOptions = map[string]argumentHandler{ "7d", }, nil, cobra.ShellCompDirectiveNoFileComp}, + // Hide session info when setting a session + "session.hide": {"session.hide", "bool", "settings.session.hide", []string{ + "true", + "false", + }, nil, cobra.ShellCompDirectiveNoFileComp}, + // cache "defaults.cache": {"defaults.cache", "bool", config.SettingsDefaultsCacheEnabled, []string{ "true", diff --git a/pkg/config/cliConfiguration.go b/pkg/config/cliConfiguration.go index 6ac4d74d8..d21af1d23 100644 --- a/pkg/config/cliConfiguration.go +++ b/pkg/config/cliConfiguration.go @@ -307,6 +307,9 @@ const ( // SettingsSessionTokenValidFor interval which the token must be valid for in order to reuse it SettingsSessionTokenValidFor = "settings.session.tokenValidFor" + // SettingsSessionHide hide sensitive information in the session banner + SettingsSessionHide = "settings.session.hide" + // Cache settings // SettingsDefaultsCacheEnabled enable caching SettingsDefaultsCacheEnabled = "settings.defaults.cache" @@ -532,6 +535,7 @@ func (c *Config) bindSettings() { // Session options WithBindEnv(SettingsSessionAlwaysIncludePassword, false), WithBindEnv(SettingsSessionTokenValidFor, "8h"), + WithBindEnv(SettingsSessionHide, false), WithBindEnv(SettingsBrowser, ""), @@ -1513,6 +1517,11 @@ func (c *Config) HideSensitive() bool { return c.viper.GetBool(SettingsLoggerHideSensitive) } +// HideSessionBanner hide sensitive information in the session banner +func (c *Config) HideSessionBanner() bool { + return c.viper.GetBool(SettingsSessionHide) +} + // DisableStdin hide sensitive information in log entries func (c *Config) DisableStdin() bool { return c.viper.GetBool(SettingsDisableInput) @@ -1978,11 +1987,14 @@ func (c *Config) ReadConfigFiles(client *c8y.Client) (path string, err error) { } func (c *Config) HideSensitiveInformationIfActive(client *c8y.Client, message string) string { - if client == nil { + if !c.HideSensitive() { return message } + return c.HideSensitiveInformation(client, message) +} - if !c.HideSensitive() { +func (c *Config) HideSensitiveInformation(client *c8y.Client, message string) string { + if client == nil { return message } @@ -1991,22 +2003,20 @@ func (c *Config) HideSensitiveInformationIfActive(client *c8y.Client, message st message = strings.ReplaceAll(message, username, "******") } - if client != nil { - if client.TenantName != "" { - message = strings.ReplaceAll(message, client.TenantName, "{tenant}") - } - if client.Username != "" { - message = strings.ReplaceAll(message, client.Username, "{username}") - } - if client.Password != "" { - message = strings.ReplaceAll(message, client.Password, "{password}") - } - if client.Token != "" { - message = strings.ReplaceAll(message, client.Token, "{token}") - } - if client.BaseURL != nil { - message = strings.ReplaceAll(message, strings.TrimRight(client.BaseURL.Host, "/"), "{host}") - } + if client.TenantName != "" { + message = strings.ReplaceAll(message, client.TenantName, "{tenant}") + } + if client.Username != "" { + message = strings.ReplaceAll(message, client.Username, "{username}") + } + if client.Password != "" { + message = strings.ReplaceAll(message, client.Password, "{password}") + } + if client.Token != "" { + message = strings.ReplaceAll(message, client.Token, "{token}") + } + if client.BaseURL != nil { + message = strings.ReplaceAll(message, strings.TrimRight(client.BaseURL.Host, "/"), "{host}") } basicAuthMatcher := regexp.MustCompile(`(Basic\s+)[A-Za-z0-9=]+`) diff --git a/tests/manual/sessions/login/session_login.yaml b/tests/manual/sessions/login/session_login.yaml index e2141b864..0318448c0 100644 --- a/tests/manual/sessions/login/session_login.yaml +++ b/tests/manual/sessions/login/session_login.yaml @@ -26,3 +26,31 @@ tests: version: r/^.+$ username: r/^.+$ token: r/^.+$ + + It does not hide sensitive info in the session banner by default: + command: | + c8y sessions login --from-env + exit-code: 0 + stderr: + not-contains: + - "{host}" + - "{tenant}" + - "{username}" + - "hidden" + + It hides sensitive information in the session banner: + command: | + c8y sessions login --from-env + exit-code: 0 + config: + retries: 0 + inherit-env: false + env: + C8Y_SETTINGS_SESSION_HIDE: true + stderr: + contains: + - "✓ Session is now active" + - --------------------- Cumulocity Session (sensitive info is hidden) --------------------- + - "host : {host}" + - "tenant : {tenant}" + - "username : {username}"