Skip to content

[WiP] mysql proxy: separate downstream client and upstream server authentication.#15485

Closed
qinggniq wants to merge 26 commits intoenvoyproxy:mainfrom
qinggniq:mysql-tool
Closed

[WiP] mysql proxy: separate downstream client and upstream server authentication.#15485
qinggniq wants to merge 26 commits intoenvoyproxy:mainfrom
qinggniq:mysql-tool

Conversation

@qinggniq
Copy link
Copy Markdown
Contributor

@qinggniq qinggniq commented Mar 15, 2021

Signed-off-by: qinggniq livewithblank@gmail.com

For an explanation of how to fill out the fields, please see the relevant section
in PULL_REQUESTS.md

This pr implements the separate downstream client and upstream server authentication in mysql proxy.
How it works?

  • maintain a mysql client pool per thread, establish a valid mysql client is expensive, it need one tcp handshake and at least 1.5 round trip server greet -> client auth -> server response, if server need client switch auth plugin, it need additional 1 round trip. So I decide to maintain a mysql client pool for each cluster, once mysql client is released, it will be put back into pool.
  • when downstream connection is coming, mysql proxy acts as a server, sends greet message to client, wait for client auth message, then verify the auth message by downstream_auth_username, downstream_auth_password. Once client pass the authentication, base on the db in auth message, proxy will find corresponding cluster, and get a client from mysql client pool.
  • when proxy is acquiring a client, the mysql client pool first check whether there are any idle clients, if not it will use underlaying Tcp::ConnectionPool::Instance to acquire a connection, when success, it will mmic as a mysql client, deal with greet message, send auth message, when received ok message which means it pass the connection phase, the pool will put this connection into active clients list.

What will it affect?

  • Now mysql proxy needs to maintain connections of upstream like tcp proxy or redis proxy, so it become a terminal filter. In current mysql proxy documentation, it mentions that it's Dynamic Metadata could combine with rbac filter to control accesses to individual tables in a database.

but now mysql proxy becomes a terminal filter, so this feature is no longer support, but when mysql proxy maintain the connections of upstream, it becomes possible to support more features like mirror, fault, read load balance.

Commit Message: separate downstream client and upstream server authentication.
Additional Description:
Risk Level: medium
Testing: TBD
Docs Changes: TBD
Release Notes: support separate downstream client and upstream server authentication.
Platform Specific Features: N/A
[Optional Runtime guard:]
[Optional Fixes #Issue] #14697
[Optional Deprecated:]
[Optional API Considerations:]

Signed-off-by: qinggniq <livewithblank@gmail.com>
@repokitteh-read-only
Copy link
Copy Markdown

CC @envoyproxy/api-shepherds: Your approval is needed for changes made to api/envoy/.
API shepherd assignee is @htuch
CC @envoyproxy/api-watchers: FYI only for changes made to api/envoy/.

🐱

Caused by: #15485 was opened by qinggniq.

see: more, trace.

@qinggniq qinggniq marked this pull request as draft March 15, 2021 06:00
Signed-off-by: qinggniq <livewithblank@gmail.com>
…hentication

Signed-off-by: qinggniq <livewithblank@gmail.com>
@htuch
Copy link
Copy Markdown
Member

htuch commented Mar 17, 2021

@qinggniq I don't understand:

this feature will make the mysql filter to be terminal filter, thus the previous feature collect query stat and use rbac filter to deny specific queries is no longer support.

for two reasons:

  1. RBAC comes before any terminal filter, so why wouldn't this keep working?
  2. From an API compatibility perspective, I don't think we can do this as you are effectively making an API breaking change. It's also not very clearly documented in the PR.

google.protobuf.UInt32Value max_idle_connections = 2;

// Connections when envoy setup, default is 0.
google.protobuf.UInt32Value start_connections = 3;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What do these changes have to do with auth?

@qinggniq
Copy link
Copy Markdown
Contributor Author

qinggniq commented Mar 17, 2021

@htuch Hello, I filled up the pr message, just ping me if the message has any confused points.

Signed-off-by: qinggniq <livewithblank@gmail.com>
}

message MySQLProtocolOptions {
config.core.v3.DataSource auth_password = 1 [(udpa.annotations.sensitive) = true];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggest a shared message for username/password.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Do you mean remove udpa.annotations.sensitive = true? I just found at redis username/password is secret at

message RedisProtocolOptions {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.filter.network.redis_proxy.v2.RedisProtocolOptions";
// Upstream server password as defined by the `requirepass` directive
// <https://redis.io/topics/config>`_ in the server's configuration file.
config.core.v3.DataSource auth_password = 1 [(udpa.annotations.sensitive) = true];
// Upstream server username as defined by the `user` directive
// <https://redis.io/topics/acl>`_ in the server's configuration file.
config.core.v3.DataSource auth_username = 2 [(udpa.annotations.sensitive) = true];
, I don't know is there any differences between them.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm saying grouping username/password with senstitivity options into a single message is good and makes sense als for downstream above; the same username/password message can be reused for both upstream/downstream.

Copy link
Copy Markdown
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

I still think we need to maintain backwards compatibility. Otherwise, when we ship the next Envoy release, control planes will have their configuration rejected by Envoy.

This is a v3 stable filter (at API level), so we can't just say that certain uses of it are no longer supported. You need to allow the filter to function in the use cases it previously did limit the new capabilities for when it is a terminal filter.


repeated Route routes = 3;

ConnectionPoolSettings setting = 4 [(validate.rules).message = {required: true}];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you split out the connection pool aspect of this PR from the split in the auth aspect? This is a very large PR and will be hard to review unless that is done IMHO.

@htuch
Copy link
Copy Markdown
Member

htuch commented Mar 18, 2021

@rshriram @venilnoronha for high-level API and design input as well.

@qinggniq
Copy link
Copy Markdown
Contributor Author

qinggniq commented Mar 19, 2021

@htuch I think there are two approaches to make it backward compatible:

  1. Determine whether this filter is terminal filter by setting a is_terminal flag at protobuf file, we can use this to turn off the feature by default, but I'm not sure it's there are any way to implement it.
  2. Make another mysql proxy and name it to mysql_proxy_plus, reserve current mysql proxy, and replace mysql_proxy with mysql_proxy_plus completely at v4.

@htuch
Copy link
Copy Markdown
Member

htuch commented Mar 19, 2021

@qinggniq I don't think (2) is great given duplication, so you will need to add an API field to distinguish from existing behaviors like (1).

@qinggniq
Copy link
Copy Markdown
Contributor Author

qinggniq commented Mar 22, 2021

@htuch Yes, I agree that if (1) and (2) could both work, (1) will be better. I'm trying to find out a way to determine the filter whether is a terminal filter at runtime #15565. And I think the method (2) could be able not to make much duplication, the code base of mysql proxy contains many common files which can be used by two filters like codec, session, decoder, utils, the difference between mysql_proxy and mysql_proxy_plus is config、filter. I think when (1) could work, we still need to write a new MySQLFilter for new feature, because now the base classes of MySQLFilter is deferent from before, they now are "different filters".

Signed-off-by: qinggniq <livewithblank@gmail.com>
Signed-off-by: qinggniq <livewithblank@gmail.com>
…ysql-tool

Signed-off-by: qinggniq <livewithblank@gmail.com>
Signed-off-by: qinggniq <livewithblank@gmail.com>
Copy link
Copy Markdown
Contributor

@alyssawilk alyssawilk left a comment

Choose a reason for hiding this comment

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

(adding a drive by comment or two after looking this over from the runtime issue)

ConnectionPoolSettings config_;
std::list<ThreadLocalActiveClientPtr> pending_clients_;
std::list<ThreadLocalActiveClientPtr> active_clients_;
std::list<ThreadLocalActiveClientPtr> busy_clients_;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

connection reuse will be awesome! That said, this looks a lot like the legacy TCP connection pool and I don't think we want to reinvent this again, especially as the base pool supports connection reuse.

Can we please inherit from souce/common/conn_pool/conn_pool_base.* and refactor/extend where needed to avoid all the duplicate pending/active/busy work and reuse stats etc?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's better.

// Configuration for tunneling TCP over other transports or application layers.
// Tunneling is supported over both HTTP/1.1 and HTTP/2. Upstream protocol is
// determined by the cluster configuration.
// Currently, only HTTP/2 is supported. When other options exist, HTTP/2 will
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

bad merge?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, I'll fix those unrelated changes.

@htuch
Copy link
Copy Markdown
Member

htuch commented Mar 23, 2021

@venilnoronha @rshriram would one of you be able to provide some MySQL CODEOWNERS input as well into the API discussion? Thanks.

Signed-off-by: qinggniq <livewithblank@gmail.com>
Signed-off-by: qinggniq <livewithblank@gmail.com>
Signed-off-by: qinggniq <livewithblank@gmail.com>
Signed-off-by: qinggniq <livewithblank@gmail.com>
Signed-off-by: qinggniq <livewithblank@gmail.com>
Signed-off-by: qinggniq <livewithblank@gmail.com>

format

Signed-off-by: qinggniq <livewithblank@gmail.com>
Signed-off-by: qinggniq <livewithblank@gmail.com>
Copy link
Copy Markdown
Contributor Author

@qinggniq qinggniq left a comment

Choose a reason for hiding this comment

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

@htuch I have changed api a little, could you take a look?


# We already have absl in the build, define absl=1 to tell googletest to use absl for backtrace.
build --define absl=1
build --//bazel:http3=False
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I will remove it in the final.

Comment on lines +44 to +45

repeated Route routes = 3;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Here I will add a field like bool new_feature_enabled to control after I replace the signature of isTerminalFilter. #15565 (comment)

Signed-off-by: qinggniq <livewithblank@gmail.com>

fix: filter config

Signed-off-by: qinggniq <livewithblank@gmail.com>

add some comments

Signed-off-by: qinggniq <livewithblank@gmail.com>

fix typo

Signed-off-by: qinggniq <livewithblank@gmail.com>
@qinggniq qinggniq requested a review from htuch April 2, 2021 11:01
@htuch
Copy link
Copy Markdown
Member

htuch commented Apr 2, 2021

@lizan this was introduced by Tetrate I think; do you folks still consider MySQL something you are supporting?

Copy link
Copy Markdown
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

@qinggniq I left some API comments, as long as we resolve this in a way that does not radically break existing users I'm fine with that.

Re: code review, I'm trying to find someone who can take a look. The existing CODEOWNERS appear MIA and this is a large non-trivial change that needs someone with MySQL experience.

We may be able to simply treat this as an external contribution and move it under the new contrib/ directory structure that @mattklein123 is planning on putting in place. I've had a look and your code appears to be localized to just the extension, so it would naturally fit that mold.

}

message MySQLProtocolOptions {
config.core.v3.DataSource auth_password = 1 [(udpa.annotations.sensitive) = true];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm saying grouping username/password with senstitivity options into a single message is good and makes sense als for downstream above; the same username/password message can be reused for both upstream/downstream.


reserved 3;

reserved "is_termianl";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Where does this come from?

qinggniq added 3 commits April 6, 2021 13:37
Signed-off-by: qinggniq <livewithblank@gmail.com>

fix spell

Signed-off-by: qinggniq <livewithblank@gmail.com>
…-tool

Signed-off-by: qinggniq <livewithblank@gmail.com>
Signed-off-by: qinggniq <livewithblank@gmail.com>
@qinggniq
Copy link
Copy Markdown
Contributor Author

qinggniq commented Apr 6, 2021

We may be able to simply treat this as an external contribution and move it under the new contrib/ directory structure that @mattklein123 is planning on putting in place

@htuch Thanks for review, I am a little confused of above message.

  • Do you mean to move the source/extensions/filters/network/mysql_proxy to new directory contrib/extensions/filters/network/mysql_proxy?
  • When do you plan to do this?
  • What's the difference between treat this as an external contribution and improve exists extension?

// If the access log field is empty, access logs will not be written.
string access_log = 2;

// [#not-implemented-hide:] enable feature of separate authentication of downstream clients and upstream server. When feature_enable is true, the filter is a terminal filter, it's config should put at the last of filter chain, when feature_enable is false, the filter is not a terminal filter, it's config can not put at the last of filter chain. Default is false.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Needs to be wrapped and have a better name than "feature_enable"; maybe enable_protocol_management or something like that to indicate you're now basically the equivalent of HCM.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed.

// - database: "database_b"
// cluster: "cluster_b"
//
// When using the above routes, the following connect dsn would be sent to:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

DSN?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

// * If username is empty but password is not, then proxy will only auth password.
// * If password is empty but username is not, then proxy will only auth username/
// * If the username is not match, then ER_ACCESS_DENIED_ERROR will return;
// * If connect db name is not found in envoy.extensions.filters.network.mysql_proxy.v3.MySQLProxy.routes, then ER_ER_BAD_DB_ERROR will return.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What does the last bullet point have to do with auth?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

removed.


// Downstream client username/password.
// * If username and password is empty, then authentication will always pass.
// * If username is empty but password is not, then proxy will only auth password.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// * If username is empty but password is not, then proxy will only auth password.
// * If username is empty but password is not, then proxy will only match the auth password.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also, what's the point of wildcard username/passwords? Why not have exact match semantics, expecting an empty string when there is an empty username specified?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it's weird not using exact match semantics for username/password. fixed.

// Downstream client username/password.
// * If username and password is empty, then authentication will always pass.
// * If username is empty but password is not, then proxy will only auth password.
// * If password is empty but username is not, then proxy will only auth username/
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// * If password is empty but username is not, then proxy will only auth username/
// * If password is empty but username is not, then proxy will only match the auth username.


// Auth information of MySQL.
message AuthInfo {
config.core.v3.DataSource auth_username = 1;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Probably just put the sensitive annotations on the fields in here directly to factor them out.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed.

Signed-off-by: qinggniq <livewithblank@gmail.com>

fix typo

Signed-off-by: qinggniq <livewithblank@gmail.com>

fix typo

Signed-off-by: qinggniq <livewithblank@gmail.com>
Copy link
Copy Markdown
Member

@lizan lizan left a comment

Choose a reason for hiding this comment

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

In general I think the direction is fine, but this PR is too large. Is it possible to split out a couple things out of this PR such: a) adding routing/conn pool support and b) authentication support?

Comment on lines +23 to +25
config.core.v3.DataSource auth_username = 1 [(udpa.annotations.sensitive) = true];

config.core.v3.DataSource auth_password = 2 [(udpa.annotations.sensitive) = true];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can be just username/password (without auth_ prefix).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fix.

// [#not-implemented-hide:] enable manage MySQL protocol at proxy. Default is false.
// * When enable_manage_protocol is true, MySQL filter is a terminal filter, it's config should put at the last of filter chain, in the same time, MySQLProtocolOptions should be set at corresponding config of cluster for connecting to upstream database.
// * When enable_manage_protocol is false, MySQL filter is not a terminal filter, it's config can not put at the last of filter chain.
bool enable_manage_protocol = 3;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isn't this implied by whether routes has element or not? Existing behavior delegate route select to tcp_proxy so in that case route shouldn't have anything.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, when enable_manage_protocol is false, route and auth_info should not set value.

@qinggniq
Copy link
Copy Markdown
Contributor Author

qinggniq commented Apr 7, 2021

@lizan ok, I'll try to do it.

Signed-off-by: qinggniq <livewithblank@gmail.com>
lizan pushed a commit that referenced this pull request Apr 9, 2021
This pull request is the part of #15485, help reduce the work of review.
- refactor the BUILD struct of mysql proxy
- replace auth related fields of message from `string` to `vector`, avoid `\0` character leading message to be partial.

Commit Message: refactor BUILD struct of mysql proxy
Additional Description:
Risk Level: Low
Testing: N/A
Docs Changes: N/A
Release Notes: N/A

Signed-off-by: qinggniq <livewithblank@gmail.com>
Monkeyanator pushed a commit to Monkeyanator/envoy that referenced this pull request Apr 20, 2021
This pull request is the part of envoyproxy#15485, help reduce the work of review.
- refactor the BUILD struct of mysql proxy
- replace auth related fields of message from `string` to `vector`, avoid `\0` character leading message to be partial.

Commit Message: refactor BUILD struct of mysql proxy
Additional Description:
Risk Level: Low
Testing: N/A
Docs Changes: N/A
Release Notes: N/A

Signed-off-by: qinggniq <livewithblank@gmail.com>
@github-actions
Copy link
Copy Markdown

github-actions bot commented May 7, 2021

This pull request has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed in 7 days if no further activity occurs. Please feel free to give a status update now, ping for review, or re-open when it's ready. Thank you for your contributions!

@github-actions github-actions bot added the stale stalebot believes this issue/PR has not been touched recently label May 7, 2021
@github-actions
Copy link
Copy Markdown

This pull request has been automatically closed because it has not had activity in the last 37 days. Please feel free to give a status update now, ping for review, or re-open when it's ready. Thank you for your contributions!

@github-actions github-actions bot closed this May 14, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api stale stalebot believes this issue/PR has not been touched recently

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants