Skip to content

sql,cli: implement \password cli command#77975

Merged
craig[bot] merged 1 commit intocockroachdb:masterfrom
ZhouXing19:slash-password
Apr 29, 2022
Merged

sql,cli: implement \password cli command#77975
craig[bot] merged 1 commit intocockroachdb:masterfrom
ZhouXing19:slash-password

Conversation

@ZhouXing19
Copy link
Copy Markdown
Collaborator

@ZhouXing19 ZhouXing19 commented Mar 16, 2022

This commit is to add support for the \password that securely changes the
password for a user.

fixes #48543

Release note (cli change): add support for \password cli command that enables
secure alteration of the password for a user. The given password will always be pre-hashed
with the password hash method obtained via the session variable password-encryption,
i.e. scram-sha-256 as the default hashing algorithm.

@ZhouXing19 ZhouXing19 requested a review from a team as a code owner March 16, 2022 20:46
@cockroach-teamcity
Copy link
Copy Markdown
Member

This change is Reviewable

@ZhouXing19 ZhouXing19 changed the title [WIP] sql,cli: implement \password cli command [WIP] sql,cli: implement \password cli command Mar 16, 2022
@ZhouXing19 ZhouXing19 force-pushed the slash-password branch 3 times, most recently from 5b8a1ca to 86c709f Compare March 30, 2022 15:26
@ZhouXing19 ZhouXing19 force-pushed the slash-password branch 2 times, most recently from 5567b41 to 248293c Compare March 31, 2022 16:08
@ZhouXing19 ZhouXing19 changed the title [WIP] sql,cli: implement \password cli command sql,cli: implement \password cli command Mar 31, 2022
@ZhouXing19 ZhouXing19 requested review from a team and rafiss March 31, 2022 16:08
Copy link
Copy Markdown
Collaborator

@rafiss rafiss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice tests! just had a comment about hashing

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @rafiss and @ZhouXing19)


pkg/cli/clisqlshell/sql.go, line 1194 at r1 (raw file):

				`ALTER USER %s WITH LOGIN PASSWORD '%s'`,
				userName,
				stripANSIFromString(string(password1)))

if i understand the motivation of #48543 correctly, we want to avoid sending the password in cleartext here. now that #72579 is completed, the CLI can pre-hash the password and then send it in the ALTER USER command.

you can see the compareWithCleartextPassword functions in pkg/security/password.go to see the code for how to hash passwords. (the hashing code will need to be extracted to a separate function so it can be used here)

I think we want this \password command to use SCRAM. I'm not sure if it should be configurable. perhaps @knz has a thought here.

Copy link
Copy Markdown
Collaborator

@rafiss rafiss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @rafiss and @ZhouXing19)


-- commits, line 9 at r1:
this should be "Release note (cli change)"

also, add more details about what the command actually does. (i.e. just say "the command lets the current user alter their password")

also, if we do pre-hashing as per my other comment, that should be added here

Copy link
Copy Markdown
Contributor

@knz knz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 3 of 3 files at r1, all commit messages.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @rafiss and @ZhouXing19)


pkg/cli/clisqlshell/sql.go, line 1167 at r1 (raw file):

		fmt.Fprintf(c.iCtx.stdout, "Enter new password: \n")

		password1, err := term.ReadPassword(int(c.ins.Stdin().Fd()))

Please use security.PromptForPassword() here instead of rolling out your own logic.


pkg/cli/clisqlshell/sql.go, line 1194 at r1 (raw file):

Previously, rafiss (Rafi Shamim) wrote…

if i understand the motivation of #48543 correctly, we want to avoid sending the password in cleartext here. now that #72579 is completed, the CLI can pre-hash the password and then send it in the ALTER USER command.

you can see the compareWithCleartextPassword functions in pkg/security/password.go to see the code for how to hash passwords. (the hashing code will need to be extracted to a separate function so it can be used here)

I think we want this \password command to use SCRAM. I'm not sure if it should be configurable. perhaps @knz has a thought here.

Agreed with Rafi. As-is, this logic will not improve the situation too much, because customers will still complain the password is visible in SQL logs.

Pre-hashing will be the right way to avoid that.


pkg/cli/clisqlshell/sql.go, line 2132 at r1 (raw file):

}

// stripANSIFromString returns str with all ANSI escape sequences removed.

Why is this logic important? I don't understand why you added this. What problem is this supposed to solve?

@ZhouXing19 ZhouXing19 requested a review from a team as a code owner April 5, 2022 19:59
@ZhouXing19 ZhouXing19 requested a review from a team April 5, 2022 19:59
@ZhouXing19 ZhouXing19 removed request for a team April 5, 2022 20:02
Copy link
Copy Markdown
Collaborator Author

@ZhouXing19 ZhouXing19 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @knz, @rafiss, and @ZhouXing19)


pkg/cli/clisqlshell/sql.go, line 1167 at r1 (raw file):

Previously, knz (kena) wrote…

Please use security.PromptForPassword() here instead of rolling out your own logic.

Done.


pkg/cli/clisqlshell/sql.go, line 1194 at r1 (raw file):

Previously, knz (kena) wrote…

Agreed with Rafi. As-is, this logic will not improve the situation too much, because customers will still complain the password is visible in SQL logs.

Pre-hashing will be the right way to avoid that.

Added pre-hashing. Here I hard-coded the cost for hashing the cleartext with SCRAM. This is not ideal but I can't think of other better ways.


pkg/cli/clisqlshell/sql.go, line 2132 at r1 (raw file):

Previously, knz (kena) wrote…

Why is this logic important? I don't understand why you added this. What problem is this supposed to solve?

This is because when I try copying and pasting a password to the cli, ANSIF prefix and suffix are automatically added.
E.g. the password I wanted to pass was

SCRAM-SHA-256$119680:Hw/dhibRaBVbObr6bATlqw==$BTFlXuZT2ZNxA3DxogkGgK0wVlOyNtxGoqDI62hyLUo=:3eZMZdAossPInncABj7/N/jkGtRZEUrz7uGkkNibxxx=

But fmt.Fprintf(c.iCtx.stdout, "password1:%#v\n", password1), I got

password1:“\x1b[200~SCRAM-SHA-256$119680:Hw/dhibRaBVbObr6bATlqw==$BTFlXuZT2ZNxA3DxogkGgK0wVlOyNtxGoqDI62hyLUo=:3eZMZdAossPInncABj7/N/jkGtRZEUrz7uGkkNibxxx=\x1b[201~”

You can see that \x1b[200~ and \x1b[201~ are automatically added.
I think copy and paste a pre-hashed password can be a common usage, and this stripANSIFromString is to deal with this edge case.

@ZhouXing19 ZhouXing19 requested review from knz and rafiss April 5, 2022 20:14
Copy link
Copy Markdown
Contributor

@knz knz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 of 4 files at r2, 3 of 4 files at r3, 1 of 1 files at r4, all commit messages.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @rafiss and @ZhouXing19)


pkg/cli/clisqlshell/sql.go, line 2132 at r1 (raw file):

Previously, ZhouXing19 (Jane Xing) wrote…

This is because when I try copying and pasting a password to the cli, ANSIF prefix and suffix are automatically added.
E.g. the password I wanted to pass was

SCRAM-SHA-256$119680:Hw/dhibRaBVbObr6bATlqw==$BTFlXuZT2ZNxA3DxogkGgK0wVlOyNtxGoqDI62hyLUo=:3eZMZdAossPInncABj7/N/jkGtRZEUrz7uGkkNibxxx=

But fmt.Fprintf(c.iCtx.stdout, "password1:%#v\n", password1), I got

password1:“\x1b[200~SCRAM-SHA-256$119680:Hw/dhibRaBVbObr6bATlqw==$BTFlXuZT2ZNxA3DxogkGgK0wVlOyNtxGoqDI62hyLUo=:3eZMZdAossPInncABj7/N/jkGtRZEUrz7uGkkNibxxx=\x1b[201~”

You can see that \x1b[200~ and \x1b[201~ are automatically added.
I think copy and paste a pre-hashed password can be a common usage, and this stripANSIFromString is to deal with this edge case.

You're solving the wrong problem, see https://en.wikipedia.org/wiki/XY_problem

The real problem is that PromptForPassword() runs in a terminal mode with paste delimiters enabled. Which is strange; it should not do this. This is what needs to be fixed.
(We have other uses of PromptForPassword() in other places. They are all equally affected. But we don't process the password string in these other places, why is that so?)


pkg/cli/clisqlshell/sql.go, line 1208 at r4 (raw file):

			isPreHashed, _, _, _, _, err := security.CheckPasswordHashValidity(context.Background(), []byte(password1))
			if err != nil {
				fmt.Fprintf(c.iCtx.stderr, "Error checking password hash validity\n:%v", err)

nit: you can remove the word error. The fact it's an error is implied.


pkg/cli/clisqlshell/sql.go, line 1214 at r4 (raw file):

			if !isPreHashed {
				if c.checkPrehashScramEnabled() {

Why do you need this? Which problem is this aiming to solve?


pkg/cli/clisqlshell/sql.go, line 1217 at r4 (raw file):

					hashedPassword, err = security.HashPasswordUsingSCRAM(context.Background(), scramIterCount, password1)
					if err != nil {
						fmt.Fprintf(c.iCtx.stderr, "error hash password using scram\n:%v", err)

ditto, replace hash by hashing


pkg/cli/clisqlshell/sql.go, line 2175 at r4 (raw file):

// `store_client_pre_hashed_passwords_enabled` is set true.
func (c *cliState) checkPrehashScramEnabled() bool {
	passwrodEncrptMethod, err := c.getSessionVarValue("password_encryption")

passwrod -> password


pkg/security/password.go, line 433 at r4 (raw file):

// HashPasswordUsingSCRAM hashes the cleartext password with given cost with SCRAM.
func HashPasswordUsingSCRAM(ctx context.Context, cost int, cleartext string) ([]byte, error) {

Why introducing a new function instead of exporting the function that's already there?

Copy link
Copy Markdown
Collaborator

@rafiss rafiss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @knz, @rafiss, and @ZhouXing19)


pkg/cli/clisqlshell/sql.go, line 1214 at r4 (raw file):

Previously, knz (kena) wrote…

Why do you need this? Which problem is this aiming to solve?

i believe the if !isPrehashed check i believe is needed because the user themselves may type in a pre-hashed password at the CLI prompt

and the c.checkPrehashScramEnabled() check is needed because we should not convert the plaintext into a hash unless the server supports pre-hashed passwords (and SCRAM) in the ALTER USER command.

Copy link
Copy Markdown
Contributor

@knz knz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @rafiss and @ZhouXing19)


pkg/cli/clisqlshell/sql.go, line 1214 at r4 (raw file):

the if !isPrehashed check i believe is needed because the user themselves may type in a pre-hashed password at the CLI prompt

Ok, this is not a realistic use case. Interactive password entry via \password must never be interpreted as pre-hashed. If a client already has a prehashed password, they will use ALTER USER WITH PASSWORD.

the c.checkPrehashScramEnabled() check is needed because we should not convert the plaintext into a hash unless the server supports pre-hashed passwords (and SCRAM) in the ALTER USER command.

Ok that sounds mildly more reasonable, but:

  1. that check can be done just once when the session is opened instead of every time \password is invoked. We already store a couple of other server-side parameters.

  2. we only need to check whether SCRAM is enabled though. The "recognize pre-hashed passwords" is enabled by default and I think the CLI \password option should always assume it is enabled. If an operator disables it, that is non-standard deployment, and I don't think we care much that \password will "not work" in that case.

@ZhouXing19
Copy link
Copy Markdown
Collaborator Author

pkg/cli/clisqlshell/sql.go, line 1214 at r4 (raw file):

Interactive password entry via \password must never be interpreted as pre-hashed. If a client already has a prehashed password, they will use ALTER USER WITH PASSWORD.

Could you explain more on this one?

@knz
Copy link
Copy Markdown
Contributor

knz commented Apr 5, 2022

Could you explain more on this one?

\password is a convenience function for humans who want to enter a password that they have in their head.

Humans do not hold password hashes in their head. They store plaintext passwords in their head. That's the input we're guaranteed to get at the prompt.

Copy link
Copy Markdown
Collaborator

@rafiss rafiss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, with some small nits

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @knz, @rafiss, and @ZhouXing19)


pkg/cli/clisqlshell/sql.go, line 1198 at r11 (raw file):

			return errState
		} else {
			var userName string

nit: this could be var userName security.SQLUsername, which is a bit safer (see below)


pkg/cli/clisqlshell/sql.go, line 1201 at r11 (raw file):

			if len(cmd) > 1 {
				userName = cmd[1]

would need to use userName = security.MakeSQLUsernameFromUserInput(cmd[1], security.UsernameValidation)


pkg/cli/clisqlshell/sql.go, line 1209 at r11 (raw file):

			}

			var hashedPassword = []byte(password1)

nit: i think this should just be initialized as var hashedPassword []byte


pkg/cli/clisqlshell/sql.go, line 1212 at r11 (raw file):

			sv := cluster.MakeClusterSettings().SV
			hashedPassword, err := security.HashPassword(context.Background(), &sv, c.passwordHashMethod, password1)

i may have missed this from the other comment thread, but why don't we need to make sure the server supports pre-hashed passwords? (i.e. expose the server.user_login.store_client_pre_hashed_passwords.enabled setting in some way)


pkg/cli/clisqlshell/sql.go, line 1223 at r11 (raw file):

				`ALTER USER %s WITH LOGIN PASSWORD '%s'`,
				userName,
				string(hashedPassword))

i think to be extra safe, this can be:

			c.concatLines = fmt.Sprintf(
				`ALTER USER %s WITH LOGIN PASSWORD %s`,
				userName.SQLIdentifier(),
				lexbase.EscapeSQLString(string(hashedPassword)),
			)

pkg/security/password.go, line 429 at r11 (raw file):

// using the currently configured method.
func HashPassword(
	ctx context.Context, sv *settings.Values, method HashMethod, password string,

nit: this diff might be in the wrong commit

Copy link
Copy Markdown
Contributor

@knz knz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 of 4 files at r10, 4 of 4 files at r11, all commit messages.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @rafiss and @ZhouXing19)


pkg/cli/clisqlshell/sql.go, line 210 at r11 (raw file):

	// passwordHashMethod saves the hash method used for password encryption.
	// This is used by the `\password` cli command.
	passwordHashMethod security.HashMethod

I don't think cliState is the right place - that gets reset when the user uses \i (recursive execution). The c.iCtx is the one that's shared.


pkg/cli/clisqlshell/sql.go, line 1172 at r11 (raw file):

	case `\password`:
		if c.passwordHashMethod == security.HashInvalidMethod ||
			c.passwordHashMethod == security.HashMissingPassword {

I don't think it's possible to get HashMissingPassword here ever.


pkg/cli/clisqlshell/sql.go, line 1201 at r11 (raw file):

Previously, rafiss (Rafi Shamim) wrote…

would need to use userName = security.MakeSQLUsernameFromUserInput(cmd[1], security.UsernameValidation)

good catch 💯


pkg/cli/clisqlshell/sql.go, line 1212 at r11 (raw file):

Previously, rafiss (Rafi Shamim) wrote…

i may have missed this from the other comment thread, but why don't we need to make sure the server supports pre-hashed passwords? (i.e. expose the server.user_login.store_client_pre_hashed_passwords.enabled setting in some way)

My argument for this is that it's not the responsibility of the CLI to support this setting. I don't think we care to support more functionality here than a user would get if they connected to crdb via psql.


pkg/cli/clisqlshell/sql.go, line 1223 at r11 (raw file):

Previously, rafiss (Rafi Shamim) wrote…

i think to be extra safe, this can be:

			c.concatLines = fmt.Sprintf(
				`ALTER USER %s WITH LOGIN PASSWORD %s`,
				userName.SQLIdentifier(),
				lexbase.EscapeSQLString(string(hashedPassword)),
			)

woah yes good catch 💯

Also, separately, any reason why this code can't use a placeholder for the password instead? i.e. ALTER USER ... WITH LOGIN PASSWORD $1?


pkg/security/password.go, line 157 at r11 (raw file):

func GetHashMethodFromString(s string) (HashMethod, error) {
	switch s {
	case "<invalid>":

please remove the two first cases. They are not valid input.

Copy link
Copy Markdown
Collaborator Author

@ZhouXing19 ZhouXing19 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @knz and @rafiss)


pkg/cli/clisqlshell/sql.go, line 210 at r11 (raw file):

Previously, knz (kena) wrote…

I don't think cliState is the right place - that gets reset when the user uses \i (recursive execution). The c.iCtx is the one that's shared.

Done.


pkg/cli/clisqlshell/sql.go, line 1172 at r11 (raw file):

Previously, knz (kena) wrote…

I don't think it's possible to get HashMissingPassword here ever.

Removed.


pkg/cli/clisqlshell/sql.go, line 1198 at r11 (raw file):

Previously, rafiss (Rafi Shamim) wrote…

nit: this could be var userName security.SQLUsername, which is a bit safer (see below)

Done.


pkg/cli/clisqlshell/sql.go, line 1201 at r11 (raw file):

Previously, knz (kena) wrote…

good catch 💯

Done.


pkg/cli/clisqlshell/sql.go, line 1209 at r11 (raw file):

Previously, rafiss (Rafi Shamim) wrote…

nit: i think this should just be initialized as var hashedPassword []byte

Done.


pkg/cli/clisqlshell/sql.go, line 1212 at r11 (raw file):

Previously, knz (kena) wrote…

My argument for this is that it's not the responsibility of the CLI to support this setting. I don't think we care to support more functionality here than a user would get if they connected to crdb via psql.

Also, postgres always takes the input password as clear text and always encrypts it, see here and here.


pkg/cli/clisqlshell/sql.go, line 1223 at r11 (raw file):

Previously, knz (kena) wrote…

woah yes good catch 💯

Also, separately, any reason why this code can't use a placeholder for the password instead? i.e. ALTER USER ... WITH LOGIN PASSWORD $1?

Made the changes.
I didn't use a placeholder because only c.concatLines(a string) is passed to clisqlclient.MakeQuery() when the statement is being executed.

	c.exitErr = c.runWithInterruptableCtx(func(ctx context.Context) error {
		return c.sqlExecCtx.RunQueryAndFormatResults(ctx,
			c.conn, c.iCtx.stdout, c.iCtx.stderr,
			clisqlclient.MakeQuery(c.concatLines))
	})

I'm not sure how to achieve this with a placeholder for the password.


pkg/security/password.go, line 157 at r11 (raw file):

Previously, knz (kena) wrote…

please remove the two first cases. They are not valid input.

Done.


pkg/security/password.go, line 429 at r11 (raw file):

Previously, rafiss (Rafi Shamim) wrote…

nit: this diff might be in the wrong commit

Hm I see it's sitting in security: let HashPassword take a HashMethod as an argument, which is intended and seems correct to me?

@ZhouXing19 ZhouXing19 requested review from knz and rafiss April 14, 2022 15:49
Copy link
Copy Markdown
Collaborator

@rafiss rafiss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @knz, @rafiss, and @ZhouXing19)


pkg/cli/clisqlshell/sql.go, line 1212 at r11 (raw file):
well the big difference is that PG doesn't have anything like the server.user_login.store_client_pre_hashed_passwords.enabled setting.

so the PG server unconditionally does the following logic:

If the presented password string is already in MD5-encrypted or SCRAM-encrypted format, then it is stored as-is

and that is why the psql CLI can unconditionally pre-hash the password when using \password

However, if server.user_login.store_client_pre_hashed_passwords.enabled is false in CRDB, then the server will receive this pre-hashed password, and then hash it again at this point:

if security.AutoDetectPasswordHashes.Get(&st.SV) {
var isPreHashed, schemeSupported bool
var schemeName string
var issueNum int
isPreHashed, schemeSupported, issueNum, schemeName, hashedPassword, err = security.CheckPasswordHashValidity(ctx, []byte(password))
if err != nil {
return hashedPassword, pgerror.WithCandidateCode(err, pgcode.Syntax)
}
if isPreHashed {
if !schemeSupported {
return hashedPassword, unimplemented.NewWithIssueDetailf(issueNum, schemeName, "the password hash scheme %q is not supported", schemeName)
}
return hashedPassword, nil
}
}
if minLength := security.MinPasswordLength.Get(&st.SV); minLength >= 1 && int64(len(password)) < minLength {
return nil, errors.WithHintf(security.ErrPasswordTooShort,
"Passwords must be %d characters or longer.", minLength)
}
hashedPassword, err = security.HashPassword(ctx, &st.SV, password)

and if that happens, the user who just changed the password will now have no way to know what the password was just changed to.

Copy link
Copy Markdown
Contributor

@knz knz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 of 4 files at r12, 4 of 4 files at r13, all commit messages.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @rafiss and @ZhouXing19)


pkg/cli/clisqlshell/sql.go, line 1212 at r11 (raw file):

Previously, rafiss (Rafi Shamim) wrote…

well the big difference is that PG doesn't have anything like the server.user_login.store_client_pre_hashed_passwords.enabled setting.

so the PG server unconditionally does the following logic:

If the presented password string is already in MD5-encrypted or SCRAM-encrypted format, then it is stored as-is

and that is why the psql CLI can unconditionally pre-hash the password when using \password

However, if server.user_login.store_client_pre_hashed_passwords.enabled is false in CRDB, then the server will receive this pre-hashed password, and then hash it again at this point:

if security.AutoDetectPasswordHashes.Get(&st.SV) {
var isPreHashed, schemeSupported bool
var schemeName string
var issueNum int
isPreHashed, schemeSupported, issueNum, schemeName, hashedPassword, err = security.CheckPasswordHashValidity(ctx, []byte(password))
if err != nil {
return hashedPassword, pgerror.WithCandidateCode(err, pgcode.Syntax)
}
if isPreHashed {
if !schemeSupported {
return hashedPassword, unimplemented.NewWithIssueDetailf(issueNum, schemeName, "the password hash scheme %q is not supported", schemeName)
}
return hashedPassword, nil
}
}
if minLength := security.MinPasswordLength.Get(&st.SV); minLength >= 1 && int64(len(password)) < minLength {
return nil, errors.WithHintf(security.ErrPasswordTooShort,
"Passwords must be %d characters or longer.", minLength)
}
hashedPassword, err = security.HashPassword(ctx, &st.SV, password)

and if that happens, the user who just changed the password will now have no way to know what the password was just changed to.

ok so my position is and remains that there's no use case for anyone to use server.user_login.store_client_pre_hashed_passwords.enabled ever. I think the deployments using this will be in the minority (if any exist at all -- we could add telemetry to confirm this)

We only added the cluster setting as a troubleshooting mechanism in case something goes wrong in the logic (i.e. a bug on our side). If we ever find a bug, we can tell the affected customer(s) "you can use this cluster setting as a stop-gap until we fix the bug".

I am not keen adding extra logic in the CLI in the common path for a code path that's only used when troubleshooting a bug which we don't even know we have (and our testing suggests it's not even there).

If this remains a point of contention, I'd nearly tempted to suggest removing that cluster setting altogether -- if there's any bug, a customer could use INSERT INTO system.users manually. We could just make ALTER detect the hashes unconditionally.


pkg/cli/clisqlshell/sql.go, line 1223 at r11 (raw file):

Previously, ZhouXing19 (Jane Xing) wrote…

Made the changes.
I didn't use a placeholder because only c.concatLines(a string) is passed to clisqlclient.MakeQuery() when the statement is being executed.

	c.exitErr = c.runWithInterruptableCtx(func(ctx context.Context) error {
		return c.sqlExecCtx.RunQueryAndFormatResults(ctx,
			c.conn, c.iCtx.stdout, c.iCtx.stderr,
			clisqlclient.MakeQuery(c.concatLines))
	})

I'm not sure how to achieve this with a placeholder for the password.

yes, thanks for explaining.


pkg/cli/clisqlshell/sql.go, line 1188 at r13 (raw file):

		}

		fmt.Fprintf(c.iCtx.stdout, "password1:%#v\npassword2:%#v\n", password1, password2)

don't forget to remove this before this merges


pkg/cli/clisqlshell/sql.go, line 1206 at r13 (raw file):

			}
			if err != nil {
				fmt.Fprintf(c.iCtx.stderr, "normalize username: %v\n", err)

i don't think this fprintf is necessary. The error will never happen unless the user is injecting some incompatible and erroneous network tool in-between the CLI and the server.
In fact, I would be in favor of adding a new line instead

err = errors.NewAssertionFailureWithWrappedErrf(err, "server-side username does not pass validation")

Copy link
Copy Markdown
Collaborator

@rafiss rafiss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm after addressing Raphael's nits

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @rafiss and @ZhouXing19)


pkg/cli/clisqlshell/sql.go, line 1212 at r11 (raw file):

there's no use case for anyone to use server.user_login.store_client_pre_hashed_passwords.enabled ever. I think the deployments using this will be in the minority (if any exist at all -- we could add telemetry to confirm this)

i wasn't aware of this, thanks for the info. OK, I'm fine to not check in the CLI then

However, i'd then argue that server.user_login.store_client_pre_hashed_passwords.enabled should not be a public setting. that can be discussed/addressed later


pkg/security/password.go, line 429 at r11 (raw file):

Previously, ZhouXing19 (Jane Xing) wrote…

Hm I see it's sitting in security: let HashPassword take a HashMethod as an argument, which is intended and seems correct to me?

i must have seen the wrong thing, my bad

Copy link
Copy Markdown
Contributor

@knz knz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @knz, @rafiss, and @ZhouXing19)


pkg/cli/clisqlshell/sql.go, line 1212 at r11 (raw file):

Previously, rafiss (Rafi Shamim) wrote…

there's no use case for anyone to use server.user_login.store_client_pre_hashed_passwords.enabled ever. I think the deployments using this will be in the minority (if any exist at all -- we could add telemetry to confirm this)

i wasn't aware of this, thanks for the info. OK, I'm fine to not check in the CLI then

However, i'd then argue that server.user_login.store_client_pre_hashed_passwords.enabled should not be a public setting. that can be discussed/addressed later

#79954

Copy link
Copy Markdown
Collaborator Author

@ZhouXing19 ZhouXing19 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @knz, @rafiss, and @ZhouXing19)


pkg/cli/clisqlshell/sql.go, line 1188 at r13 (raw file):

Previously, knz (kena) wrote…

don't forget to remove this before this merges

Ah thanks! removed.


pkg/cli/clisqlshell/sql.go, line 1206 at r13 (raw file):

Previously, knz (kena) wrote…

i don't think this fprintf is necessary. The error will never happen unless the user is injecting some incompatible and erroneous network tool in-between the CLI and the server.
In fact, I would be in favor of adding a new line instead

err = errors.NewAssertionFailureWithWrappedErrf(err, "server-side username does not pass validation")

Made the change.

Copy link
Copy Markdown
Contributor

@knz knz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

Reviewed 1 of 1 files at r14, all commit messages.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @rafiss and @ZhouXing19)

@ZhouXing19
Copy link
Copy Markdown
Collaborator Author

The coming commits are to fix for the CI tests.

@rafiss
Copy link
Copy Markdown
Collaborator

rafiss commented Apr 21, 2022

Summarizing a convo I had with Jane:

The failing test is TestLint/TestForbiddenImportsSQLShell. This change is making cockroach-sql pull in new dependencies: pkg/roachpb,pkg/util/log, pkg/util/stop, and pkg/util/tracing. These are not allowed since it makes the lightweight SQL shell binary too large.

It's caused by the import of the security and settings/cluster packages in clisqlshell. So it seems like the options are:

  1. find another way to inject the security dependency at runtime
  2. refactor the security package so that there's a new security/password sub-package, which does not import settings/cluster or any other large database packages.

I feel like option 2 might be better.

@knz
Copy link
Copy Markdown
Contributor

knz commented Apr 21, 2022

the new sub-package only needs to contain the default values for the cluster settings (as go constants).

Importing both security and the new package would work. The problem was created with the attempt to access cluster settings (to retrieve the default value). Once we remove the code that accesses cluster settings, the dep will be dropped. This can be replaced by code that uses the new package to read just go constants.

@knz
Copy link
Copy Markdown
Contributor

knz commented Apr 21, 2022

nb: i was mistaken in my above assessment. Turns out rafi was right from the start (my apologies). I had a separate discussion with jane about this.

@ZhouXing19
Copy link
Copy Markdown
Collaborator Author

Per the suggestions above, we will be working on #80332 first to move the password logic to a separate package, security/password. Once #80332 is finished, we will rework this PR.

@knz knz force-pushed the slash-password branch from 1fe340b to 725b245 Compare April 28, 2022 22:43
@knz
Copy link
Copy Markdown
Contributor

knz commented Apr 28, 2022

Jane and I did some pair programming on this to rebase it on top of #80332.

We were able to simplify as follows:

  • the username does not need to be validated using package security, because we already take care of quoting it properly before passing it to ALTER
  • we can move the choice of default cost to the new password package

As a result, the clisqlshell package neither needs to depend on security nor on clustersettings. This was the missing simplification needed to satisfy the CI linter.

Now this PR can be merged (assuming CI does not find more problems).

@knz
Copy link
Copy Markdown
Contributor

knz commented Apr 28, 2022

also need to rebase on top of #80760

need to then adjust code in clisqlshell/sql.go as follows:

       hashedPassword, err = password.HashPassword(
               context.Background(),
               passwordHashMethod.GetDefaultCost(),
               passwordHashMethod,
               password1,
               nil, /* hashSem - no need to throttle hashing in CLI */
       )

This commit is to add support for the \password that securely changes the
password for a user.

Fixes cockroachdb#48543.

Release note (cli change): add support for \password cli command that enables
secure alteration of the password for a user. The given password will always be pre-hashed
with the password hash method obtained via the session variable password-encryption,
e.g. scram-sha-256 as the default hashing algorithm.
@ZhouXing19
Copy link
Copy Markdown
Collaborator Author

bors r+

@craig
Copy link
Copy Markdown
Contributor

craig bot commented Apr 29, 2022

Build succeeded:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security: support for setting passwords securely in the sql client

4 participants