-
Notifications
You must be signed in to change notification settings - Fork 10.4k
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
Added system roots feature to load roots from OS trust store #16083
Added system roots feature to load roots from OS trust store #16083
Conversation
Thanks @tdbhacks for this PR. |
|
|
|
|
|
|
|
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My main concern here is that the platform-specificity is being handled the wrong way. The way this PR is now, it won't even build on non-Linux platforms.
Please let me know if you have any questions.
Reviewed 7 of 8 files at r1, 1 of 1 files at r3.
Reviewable status: all files reviewed, 15 unresolved discussions (waiting on @tdbhacks and @yihuazhang)
src/core/lib/security/security_connector/security_connector.h, line 45 at r3 (raw file):
PLATFORM_WINDOWS, PLATFORM_TEST } grpc_platform;
This enum doesn't make sense. We already know what platform we're built on, so let's just compile in the right implementation for the platform. There should be no need for callers to tell us this at run-time.
For an example of how to do this, see:
https://github.com/grpc/grpc/blob/master/src/core/lib/iomgr/gethostname.h
The API is the same on all platforms, but there's a different file to implement it for each platform:
https://github.com/grpc/grpc/blob/master/src/core/lib/iomgr/gethostname_host_name_max.cc
https://github.com/grpc/grpc/blob/master/src/core/lib/iomgr/gethostname_sysconf.cc
And there's a default implementation used if we don't have one for the platform:
https://github.com/grpc/grpc/blob/master/src/core/lib/iomgr/gethostname_fallback.cc
The implementations are selected via the GRPC_POSIX_SYSCONF
or GRPC_POSIX_HOST_NAME_MAX
preprocessor macros, which are defined here:
https://github.com/grpc/grpc/blob/master/src/core/lib/iomgr/port.h
I think we should use a similar structure for the feature in this PR.
src/core/lib/security/security_connector/security_connector.h, line 275 at r3 (raw file):
// Returns a grpc_slice containing OS-specific root certificates. // Protected for testing. static grpc_slice GetSystemRootCerts();
None of these methods should be present here. Instead, there should be a single interface called something like LoadSystemRootCerts()
, with different implementations for each platform. That function can then be called from the code here.
src/core/lib/security/security_connector/security_connector.h, line 326 at r3 (raw file):
// List of possible Linux certificate files and directories. static const char* linux_cert_files_[];
These three constants should be in a platform-specific file.
src/core/lib/security/security_connector/security_connector.cc, line 64 at r3 (raw file):
#endif /* --- Flag to enable/disable system root certificates feature --- */
Is it the intent that if you want to disable this, you define GRPC_USE_SYSTEM_SSL_ROOTS
to some other environment variable name that is not expected to actually be defined? If so, this seems very cumbersome. It's also not guaranteed to work, because you have no guarantee that any other environment variable name does not actually exist.
Instead, I suggest having an environment variable whose value is a bool to enable or disable this behavior. We will always check this environment variable. Initially, it can default to off, so people need to opt in -- if it's not set, nothing changes. Later, when we're confident this is working right, we can consider changing the default to on, so that people need to explicitly opt out.
You can evaluate the boolean value with gpr_is_true()
, defined here:
https://github.com/grpc/grpc/blob/master/src/core/lib/gpr/string.h#L117
src/core/lib/security/security_connector/security_connector.cc, line 1261 at r3 (raw file):
case PLATFORM_LINUX: { grpc_slice valid_bundle_slice = grpc_empty_slice(); size_t num_cert_files_ = sizeof(DefaultSslRootStore::linux_cert_files_);
I think you want to use GPR_ARRAY_SIZE()
here.
src/core/lib/security/security_connector/security_connector.cc, line 1286 at r3 (raw file):
// TODO: this function can return a slice to avoid opening the dir twice. DIR* directory; const char* custom_dir = gpr_getenv(GRPC_SYSTEM_SSL_ROOTS_DIR);
Why are we reading this env var again, if we've already done so above?
src/core/lib/security/security_connector/security_connector.cc, line 1292 at r3 (raw file):
size_t num_cert_dirs_ = sizeof(DefaultSslRootStore::linux_cert_directories_); for (size_t i = 0; i < num_cert_dirs_; i++) { directory = opendir(linux_cert_directories_[i]);
It's cheaper to stat()
and then use S_ISDIR()
on the result than it is to actually open the directory.
src/core/lib/security/security_connector/security_connector.cc, line 1308 at r3 (raw file):
size_t valid_file_dir_len = strlen(valid_file_dir); size_t file_name_len = strlen(file_entry_name); char* absolute_path =
Why not use gpr_asprintf()
here?
Or better yet, create a buffer of size MAXPATHLEN
and write into that with snprintf()
, so that we don't need to do any allocation.
src/core/lib/security/security_connector/security_connector.cc, line 1313 at r3 (raw file):
strcat(absolute_path, "/"); strcat(absolute_path, file_entry_name); return grpc_slice_new(absolute_path, valid_file_dir_len + file_name_len + 2,
Why return this as a slice? Seems like a string would work fine, since that's the form you actually need to use it in.
src/core/lib/security/security_connector/security_connector.cc, line 1327 at r3 (raw file):
struct stat dir_entry_stat; const char* file_entry_name = directory_entry->d_name; file_path = GetAbsoluteFilePath(directory_path, file_entry_name);
We're creating a new slice in each iteration through the loop, but only unreffing it after we exit the loop. This is a memory leak for all but the last slice we see.
src/core/lib/security/security_connector/security_connector.cc, line 1328 at r3 (raw file):
const char* file_entry_name = directory_entry->d_name; file_path = GetAbsoluteFilePath(directory_path, file_entry_name); int stat_return = lstat(grpc_slice_to_c_string(file_path), &dir_entry_stat);
This is another memory leak: we are not freeing the value returned from grpc_slice_to_c_string()
.
src/core/lib/security/security_connector/security_connector.cc, line 1335 at r3 (raw file):
continue; } cert_file = fopen(grpc_slice_to_c_string(file_path), "rb");
Why open the file and seek to the end instead of just looking at dir_entry_stat.st_size
?
src/core/lib/security/security_connector/security_connector.cc, line 1366 at r3 (raw file):
struct stat dir_entry_stat; const char* file_entry_name = directory_entry->d_name; file_path = GetAbsoluteFilePath(found_cert_dir, file_entry_name);
Same comments as above regarding the memory leaks on this line and the next one.
src/core/lib/security/security_connector/security_connector.cc, line 1374 at r3 (raw file):
continue; } cert_file = fopen(grpc_slice_to_c_string(file_path), "rb");
Since you're not trying to do any line buffering here, stdio doesn't buy you anything. Suggest using the unix file API directly (i.e., open(2)
and read(2)
instead of fopen(3)
and fread(3)
).
src/core/lib/security/security_connector/security_connector.cc, line 1377 at r3 (raw file):
if (cert_file != nullptr) { // Read file into bundle. fseek(cert_file, 0, SEEK_END);
As above, no need to seek to the end to get the size; just use dir_entry_stat.st_size
.
Yes, we figured that out as soon as we sent the PR. Thank you for pointing us in the right direction, we will take a look into what you provided and change our implementation accordingly. Thanks! |
@tdbhacks, here is another example (i.e., check gcp environment) of a single-interface with multiple platform-specific implementations that Mark pointed out. Interface: You could also use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My major concern is that it is not clear to me how you select a root cert when multiple sources are available for it. Currently, four sources are available: 1) a default root cert directory, 2) custom root cert directory, 3) loading from OS, and 4) load from a gRPC source. I think we need to have a clear justification on their selection orders. If the goal is to get an up-to-date cert, shouldn't we retrieve the certs from all sources and compare their timestamps?
Other things that worth consideration:
-
when reading certs from OS, there seems existing multiple sources and the current implementation simply takes the first one that is readable which does not make sense to me. Are they guaranteed to have same contents always?
-
Have you considered caching the root cert that has been previously retrieved?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To answer the first one of your two questions, we only care about the first valid source because the list of directories we have is simply a list of places in which the OS could be storing root certs. This is distribution-specific, so we don't expect a specific distribution to be storing its root certs in more than one location. Regarding timestamps and caching, those are good points. I guess our assumption was that the OS (at least as far as Linux is concerned) is always going to have the most updated trust store. But it's something worth discussing, thanks for pointing this out.
Reviewable status: all files reviewed, 15 unresolved discussions (waiting on @tdbhacks, @markdroth, and @jiangtaoli2016)
src/core/lib/security/security_connector/security_connector.h, line 45 at r3 (raw file):
Previously, markdroth (Mark D. Roth) wrote…
This enum doesn't make sense. We already know what platform we're built on, so let's just compile in the right implementation for the platform. There should be no need for callers to tell us this at run-time.
For an example of how to do this, see:
https://github.com/grpc/grpc/blob/master/src/core/lib/iomgr/gethostname.h
The API is the same on all platforms, but there's a different file to implement it for each platform:
https://github.com/grpc/grpc/blob/master/src/core/lib/iomgr/gethostname_host_name_max.cc
https://github.com/grpc/grpc/blob/master/src/core/lib/iomgr/gethostname_sysconf.ccAnd there's a default implementation used if we don't have one for the platform:
https://github.com/grpc/grpc/blob/master/src/core/lib/iomgr/gethostname_fallback.cc
The implementations are selected via the
GRPC_POSIX_SYSCONF
orGRPC_POSIX_HOST_NAME_MAX
preprocessor macros, which are defined here:https://github.com/grpc/grpc/blob/master/src/core/lib/iomgr/port.h
I think we should use a similar structure for the feature in this PR.
Done, thanks for the useful resources. Let me know if it looks okay now.
src/core/lib/security/security_connector/security_connector.h, line 275 at r3 (raw file):
Previously, markdroth (Mark D. Roth) wrote…
None of these methods should be present here. Instead, there should be a single interface called something like
LoadSystemRootCerts()
, with different implementations for each platform. That function can then be called from the code here.
Done.
src/core/lib/security/security_connector/security_connector.h, line 326 at r3 (raw file):
Previously, markdroth (Mark D. Roth) wrote…
These three constants should be in a platform-specific file.
Done.
src/core/lib/security/security_connector/security_connector.cc, line 64 at r3 (raw file):
Previously, markdroth (Mark D. Roth) wrote…
Is it the intent that if you want to disable this, you define
GRPC_USE_SYSTEM_SSL_ROOTS
to some other environment variable name that is not expected to actually be defined? If so, this seems very cumbersome. It's also not guaranteed to work, because you have no guarantee that any other environment variable name does not actually exist.Instead, I suggest having an environment variable whose value is a bool to enable or disable this behavior. We will always check this environment variable. Initially, it can default to off, so people need to opt in -- if it's not set, nothing changes. Later, when we're confident this is working right, we can consider changing the default to on, so that people need to explicitly opt out.
You can evaluate the boolean value with
gpr_is_true()
, defined here:https://github.com/grpc/grpc/blob/master/src/core/lib/gpr/string.h#L117
Done, thanks for pointing this out.
src/core/lib/security/security_connector/security_connector.cc, line 1261 at r3 (raw file):
Previously, markdroth (Mark D. Roth) wrote…
I think you want to use
GPR_ARRAY_SIZE()
here.
Done.
src/core/lib/security/security_connector/security_connector.cc, line 1286 at r3 (raw file):
Previously, markdroth (Mark D. Roth) wrote…
Why are we reading this env var again, if we've already done so above?
Are these calls expensive? I am currently reading from the env var twice just to get the directory as a string, instead of the bool value we are now using for use_custom_dir.
src/core/lib/security/security_connector/security_connector.cc, line 1292 at r3 (raw file):
Previously, markdroth (Mark D. Roth) wrote…
It's cheaper to
stat()
and then useS_ISDIR()
on the result than it is to actually open the directory.
Done.
src/core/lib/security/security_connector/security_connector.cc, line 1308 at r3 (raw file):
Previously, markdroth (Mark D. Roth) wrote…
Why not use
gpr_asprintf()
here?Or better yet, create a buffer of size
MAXPATHLEN
and write into that withsnprintf()
, so that we don't need to do any allocation.
Done.
src/core/lib/security/security_connector/security_connector.cc, line 1313 at r3 (raw file):
Previously, markdroth (Mark D. Roth) wrote…
Why return this as a slice? Seems like a string would work fine, since that's the form you actually need to use it in.
Done.
src/core/lib/security/security_connector/security_connector.cc, line 1327 at r3 (raw file):
Previously, markdroth (Mark D. Roth) wrote…
We're creating a new slice in each iteration through the loop, but only unreffing it after we exit the loop. This is a memory leak for all but the last slice we see.
I see. This should be fixed now because we are not using slices anymore.
src/core/lib/security/security_connector/security_connector.cc, line 1328 at r3 (raw file):
Previously, markdroth (Mark D. Roth) wrote…
This is another memory leak: we are not freeing the value returned from
grpc_slice_to_c_string()
.
Fixed.
src/core/lib/security/security_connector/security_connector.cc, line 1335 at r3 (raw file):
Previously, markdroth (Mark D. Roth) wrote…
Why open the file and seek to the end instead of just looking at
dir_entry_stat.st_size
?
This was before I started using stat, thank you for pointing this out.
src/core/lib/security/security_connector/security_connector.cc, line 1366 at r3 (raw file):
Previously, markdroth (Mark D. Roth) wrote…
Same comments as above regarding the memory leaks on this line and the next one.
Done.
src/core/lib/security/security_connector/security_connector.cc, line 1374 at r3 (raw file):
Previously, markdroth (Mark D. Roth) wrote…
Since you're not trying to do any line buffering here, stdio doesn't buy you anything. Suggest using the unix file API directly (i.e.,
open(2)
andread(2)
instead offopen(3)
andfread(3)
).
Done.
src/core/lib/security/security_connector/security_connector.cc, line 1377 at r3 (raw file):
Previously, markdroth (Mark D. Roth) wrote…
As above, no need to seek to the end to get the size; just use
dir_entry_stat.st_size
.
Done.
|
|
|
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for all your help!
Reviewable status:
complete! all files reviewed, all discussions resolved (waiting on @jiangtaoli2016)
Thanks @markdroth for your help on review. |
9f93107
to
4c58cb2
Compare
Added a flag-guarded feature that allows gRPC to load TLS/SSL roots from the OS trust store. This is the Linux-specific implementation of such feature.
9df9d9c
to
bcd747d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jiangtaoli2016 We just squashed all the commits into one, let us know if it looks good to you and feel free to add the kokoro label again.
Reviewable status: 13 of 29 files reviewed, all discussions resolved (waiting on @markdroth and @jiangtaoli2016)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Release notes draft:
Added flag-guarded feature to load roots from OS Trust Store: users can now load TLS/SSL root certificates from the OS Trust Store by setting the environment variable "GRPC_USE_SYSTEM_SSL_ROOTS" to “true” / “1” (acts as a flag). In case of failure, gRPC will fallback to the hard-coded root certificates. Also introduced "GRPC_SYSTEM_SSL_ROOTS_DIR", a second environment variable that allows users to specify their own system directory containing root certificates (such directory gets used only if the system roots flag is set to true / 1). This feature is currently supported on Linux platforms only.
Reviewed 1 of 3 files at r28, 1 of 2 files at r29, 16 of 16 files at r30.
Reviewable status:complete! all files reviewed, all discussions resolved (waiting on @jiangtaoli2016)
|
|
|
|
Test failures are known failures. Merging. |
Added a flag-guarded feature that allows gRPC to load TLS/SSL
roots from the OS trust store. This is the Linux-specific
implementation of such feature.
This change is![Reviewable](https://camo.githubusercontent.com/23b05f5fb48215c989e92cc44cf6512512d083132bd3daf689867c8d9d386888/68747470733a2f2f72657669657761626c652e696f2f7265766965775f627574746f6e2e737667)