Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parameter to disable escaping the username and password for database connections #13414

Merged
merged 3 commits into from
Jan 10, 2022

Conversation

robmonte
Copy link
Member

This feature adds the ability to disable Vault escaping any special characters in the username and password fields when connecting to a database. When writing the database config, supplying disable_escaping="true" will activate this feature. It is off by default/not specified.

@robmonte robmonte requested a review from a team December 14, 2021 01:34
@hashicorp-cla
Copy link

hashicorp-cla commented Dec 14, 2021

CLA assistant check
All committers have signed the CLA.

@hsimon-hashicorp
Copy link
Contributor

Thanks for this submission! Can you please add a changelog entry? I'll get a reviewer for you as soon as I can. Thanks!

Copy link
Member

@austingebauer austingebauer left a comment

Choose a reason for hiding this comment

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

Nice job, @robmonte! Have a few questions/suggestions around where the configuration parameter should live. +1 @hsimon-hashicorp on changelog entry needed. Would also be nice to add some level of test coverage for this.

I was able to successfully test this using an ADO style connection string and MSSQL. One observation about the ADO style connection string is that we no longer warn for a password in connection_url or redact when reading the config. This is only true is the username/password is supplied directly in the connection_url, but I thought it'd be worth mentioning.

builtin/logical/database/path_config_connection.go Outdated Show resolved Hide resolved
builtin/logical/database/path_config_connection.go Outdated Show resolved Hide resolved
sdk/database/helper/connutil/sql.go Outdated Show resolved Hide resolved
@robmonte robmonte force-pushed the db-scrt-eng-conn-url-enc-param branch from 968c977 to af5a356 Compare December 20, 2021 17:24
@vercel vercel bot temporarily deployed to Preview – vault December 20, 2021 17:24 Inactive
@vercel vercel bot temporarily deployed to Preview – vault-storybook December 20, 2021 17:24 Inactive
@robmonte robmonte force-pushed the db-scrt-eng-conn-url-enc-param branch from af5a356 to c61be0e Compare December 20, 2021 19:02
@vercel vercel bot temporarily deployed to Preview – vault December 20, 2021 19:02 Inactive
@vercel vercel bot temporarily deployed to Preview – vault-storybook December 20, 2021 19:02 Inactive
@robmonte
Copy link
Member Author

Still working on updating sql_test.go

@robmonte robmonte force-pushed the db-scrt-eng-conn-url-enc-param branch from c61be0e to a91bb3d Compare December 23, 2021 01:02
@vercel vercel bot temporarily deployed to Preview – vault December 23, 2021 01:02 Inactive
@vercel vercel bot temporarily deployed to Preview – vault-storybook December 23, 2021 01:02 Inactive
@vercel vercel bot temporarily deployed to Preview – vault January 7, 2022 17:56 Inactive
@vercel vercel bot temporarily deployed to Preview – vault-storybook January 7, 2022 17:56 Inactive
Copy link
Member

@calvn calvn left a comment

Choose a reason for hiding this comment

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

One small note, otherwise looks good!

sdk/database/helper/connutil/sql.go Outdated Show resolved Hide resolved
Copy link
Member

@austingebauer austingebauer left a comment

Choose a reason for hiding this comment

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

LGTM!

@vercel vercel bot temporarily deployed to Preview – vault January 8, 2022 02:57 Inactive
@vercel vercel bot temporarily deployed to Preview – vault-storybook January 8, 2022 02:57 Inactive
@robmonte robmonte merged commit bc76edb into main Jan 10, 2022
@robmonte robmonte deleted the db-scrt-eng-conn-url-enc-param branch January 10, 2022 18:44
heppu pushed a commit to heppu/vault that referenced this pull request Jan 13, 2022
…word chars for DB connections (hashicorp#13414)

* Add a parameter that disables escaping characters in the username or password fields for secrets engines database connections

* Always disallow template variables inside the username or password
@robmonte robmonte added this to the 1.10 milestone Jan 24, 2022
joatmon08 pushed a commit that referenced this pull request Jan 25, 2022
…word chars for DB connections (#13414)

* Add a parameter that disables escaping characters in the username or password fields for secrets engines database connections

* Always disallow template variables inside the username or password
qk4l pushed a commit to qk4l/vault that referenced this pull request Feb 4, 2022
…word chars for DB connections (hashicorp#13414)

* Add a parameter that disables escaping characters in the username or password fields for secrets engines database connections

* Always disallow template variables inside the username or password
@maxb
Copy link
Contributor

maxb commented Jul 29, 2023

What problem is this code trying to solve?

	// Do not allow the username or password template pattern to be used as
	// part of the user-supplied username or password
	if strings.Contains(c.Username, "{{username}}") ||
		strings.Contains(c.Username, "{{password}}") ||
		strings.Contains(c.Password, "{{username}}") ||
		strings.Contains(c.Password, "{{password}}") {

		return nil, fmt.Errorf("username and/or password cannot contain the template variables")
	}

I just came across it in the code, and it is unclear to me why this logic was added.

@robmonte
Copy link
Member Author

robmonte commented Aug 4, 2023

What problem is this code trying to solve?

	// Do not allow the username or password template pattern to be used as
	// part of the user-supplied username or password
	if strings.Contains(c.Username, "{{username}}") ||
		strings.Contains(c.Username, "{{password}}") ||
		strings.Contains(c.Password, "{{username}}") ||
		strings.Contains(c.Password, "{{password}}") {

		return nil, fmt.Errorf("username and/or password cannot contain the template variables")
	}

I just came across it in the code, and it is unclear to me why this logic was added.

Hi Max, when working on this feature I kept getting infrequent test failures in between successful runs, like a race condition. At the time, I had narrowed it down to the ConnectionURL sometimes being a different value despite no changes.

When building the ConnectionURL, it calls the function QueryHelper which then calls strings.ReplaceAll. It was at this point that it seemed to randomly fail replacing the the old value with a new value containing the old value itself.

At the time I couldn't figure out why this was happening. I started going down this rabbit hole again because of your question here, and I think I figured out the issue this time.

To quickly test this, I first commented out the block of code restricting the use of the template values. Next, I used the TestSQLDisableEscaping test case in sql_test.go. I commented out all of the table tests and used {"{{password}}mssql{0}", "password{0}", true} as the only test value. Here is the git patch file if you'd like to run the same test:
gh.patch

I was overlooking the fact that this QueryHelper function was calling strings.ReplaceAll in a loop from a map. Since map order isn't guaranteed, it changes the result.

Replacing the username template first (the common case):

  1. Starting value: server=localhost;port=1433;user id={{username}};password={{password}};database=mydb;
  2. Replace username: server=localhost;port=1433;user id={{password}}mssql{0};password={{password}};database=mydb;
  3. Replace password: server=localhost;port=1433;user id=password{0}mssql{0};password=password{0};database=mydb;

...but sometimes, it does the password template first:

  1. Starting value: server=localhost;port=1433;user id={{username}};password={{password}};database=mydb;
  2. Replace password: server=localhost;port=1433;user id={{username}};password=password{0};database=mydb;
  3. Replace username: server=localhost;port=1433;user id={{password}}mssql{0};password=password{0};database=mydb;

The opposite case would also happen from using {{username}} in the password value.

At the time, the quickest solution was to simply disallow the use of the template variables as part of the username or password themselves. I also don't see any good coming from allowing the use of them. It just seems like it's asking for trouble.

@maxb
Copy link
Contributor

maxb commented Aug 5, 2023

Aha! Thanks for the elucidation! I had observed that all template expressions were being replaced in one call to dbutil.QueryHelper, but hadn't looked inside it to see that it is performing a separate iteration over the string for each one, rendering it liable to the kinds of effects you saw.

Disallowing the specific template expressions as replacements is a reasonable workaroud - I dislike it because of the potential of surprising someone at some point - it wouldn't be that crazy for {{password}} to appear in a test password - but I admit it is pretty unlikely to happen in practice.

@fairclothjm
Copy link
Contributor

@robmonte Yes, thanks for the info! I wonder if we should add a comment with a brief reason for this behavior? Specifically might be worth mentioning that it is only required to prevent race conditions in the tests.

maxb added a commit to maxb/vault that referenced this pull request Aug 7, 2023
Instead fix the underlying issue of `dbutil.QueryHelper` making multiple
passes over the input string, and potentially reprocessing placeholders
inserted as a result of previous substitutions.

Follow-up to hashicorp#13414
@maxb
Copy link
Contributor

maxb commented Aug 7, 2023

I wonder if we should add a comment with a brief reason for this behavior?

I propose instead we fix the underlying templating routine so the workaround is no longer required: #22224

Specifically might be worth mentioning that it is only required to prevent race conditions in the tests.

It would be possible to run into this problem in production too if usernames or passwords contained any sequences like {{name}}, {{username}}, {{password}}, {{expiration}}, as whilst this workaround guarded one particular use of dbutil.QueryHelper, there are others.

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.

None yet

7 participants