-
Notifications
You must be signed in to change notification settings - Fork 69
s2a: Single S2AChannelCredentials. #3989
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
base: main
Are you sure you want to change the base?
Changes from all commits
8ddbe44
e35914f
217737b
5f52e87
b3d457b
953e1a0
53b8b84
6b047ae
075db93
3266899
5aba3fc
fc107a5
c7536e6
71335fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -161,6 +161,23 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP | |
| @Nullable | ||
| private final ApiFunction<ManagedChannelBuilder, ManagedChannelBuilder> channelConfigurator; | ||
|
|
||
| // This is initialized once for the lifetime of the application. This enables re-using | ||
| // channels to S2A. | ||
| private static volatile ChannelCredentials s2aChannelCredentialsObject; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. qq, is this intended to be re-used only per SDK client (e.g. SDK client has multiple channels created and each channel from an instance will re-use this)? Or do you want this to be shared across each SDK client? (e.g. I have 2+ SDK clients and each have multiple channels)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is shared across all SDK clients. An application could create multiple SDK clients, and we want the S2A channel creds to be reused across all SDK clients created by an application. In other words, we want to tie the S2A channel credentials to the lifetime of the application (not to the lifetime of a single SDK client).
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok thanks! Just want to clarify. We've had some code changes in the past with Can we try and use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
So I think to do this we need to do something like this for Initialization-on-demand: the problem is that s2aConfigProvider is a non-static class member of InstantiatingGrpcChannelProvider. It's non-static since it is used to inject a config from tests. Upon reviewing this code while responding to this comment, I realized there was a bug with the double checked lock implementation: we need to be able to reset the WDYT of continuing to use double checked locking with the reset?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is testing currently the only limitation? I don't think we should block on testing unless this requires a massive refactor/ introduces a change in behavior/ etc. Is it possible to use MockStatic to mock the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lawrence, I didn't see your latest comment as I was typing this out. Let me review it.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you and I are describing the same thing. One note I mentioned is that the setter should be thread-safe. And, we should expose a reset function to restore the original value that is modified during tests, also thread-safe. Agreed that it's not ideal to expose setters like this. Happy to implement this if it's preferred over double checked locking. Will update this thread once I have made the changes in a new commit.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I see your point with restoring the original value thread safe. IIUC, this would limit us to run each test sequentially since we can't guarantee when the values are reset. I can't seem to think of a way around this (maybe there is and I'm unaware of an existing pattern). I think the double checking lock wouldn't have this issue (correct me if that's incorrect). If that's the case, I think it's probably better for the double checking lock then. I think it would be a case where it would be better for us over the initialization on demand. Thanks for brainstorming with me :)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think we can still run the tests in parallel, it's just that the reset function would be synchronized on the class, so multiple tests can't reset at the same time.
Hmm, I think double checked locking would also have this issue. This pattern also makes sure that the S2A Channel Credentials object is only initialized once (the first time it is accessed). So each test that injects its own I did implement the initialization on demand method and found the reset functions do look a bit different compared to double checked locking: Double checked locking reset: initialization on demand reset: I do think double checked locking reset is a bit more intuitive (implemented in this PR):
Given that reset login is needed in both cases, I'm wondering which pattern you prefer? Personally, I find double checked locking to be simpler to reason about in this case, but I'm also not sure of the Java-preferred way to do things in this case. Also, please feel free to correct me if I got something wrong.
NP! Thanks for working with us on this!
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I see. We need to reset the S2AChannelCredentials and not the s2aConfigProvider after each test. I don't think we need to figure out making this work for parallel tests now. I was just thinking this may be a use case to help us decide for one over the other. I don't want to hold this up over a pattern choice and I think this is fine as is as we have a deadline. My preference is to have the JVM guarantee the singleton, but that is always something we can change/ refactor later. I think we looked the possible edge cases (e.g. resetting the static value) and the challenges (unit testing) for both. I'm fine with what you have in the PR. |
||
|
|
||
| /** | ||
| * Resets the s2aChannelCredentialsObject of the {@link InstantiatingGrpcChannelProvider} class | ||
| * for testing purposes. | ||
| * | ||
| * <p>This should only be called from tests. | ||
| */ | ||
| @VisibleForTesting | ||
| static void resetS2AChannelCredentials() { | ||
| synchronized (InstantiatingGrpcChannelProvider.class) { | ||
| s2aChannelCredentialsObject = null; | ||
| } | ||
| } | ||
|
|
||
| /* | ||
| * Experimental feature | ||
| * | ||
|
|
@@ -595,43 +612,60 @@ ChannelCredentials createPlaintextToS2AChannelCredentials(String plaintextAddres | |
| * @return {@link ChannelCredentials} configured to use S2A to create mTLS connection. | ||
| */ | ||
| ChannelCredentials createS2ASecuredChannelCredentials() { | ||
| SecureSessionAgentConfig config = s2aConfigProvider.getConfig(); | ||
| String plaintextAddress = config.getPlaintextAddress(); | ||
| String mtlsAddress = config.getMtlsAddress(); | ||
| if (Strings.isNullOrEmpty(mtlsAddress)) { | ||
| // Fallback to plaintext connection to S2A. | ||
| LOG.log( | ||
| Level.INFO, | ||
| "Cannot establish an mTLS connection to S2A because autoconfig endpoint did not return a mtls address to reach S2A."); | ||
| return createPlaintextToS2AChannelCredentials(plaintextAddress); | ||
| } | ||
| // Currently, MTLS to MDS is only available on GCE. See: | ||
| // https://cloud.google.com/compute/docs/metadata/overview#https-mds | ||
| // Try to load MTLS-MDS creds. | ||
| File rootFile = new File(MTLS_MDS_ROOT_PATH); | ||
| File certKeyFile = new File(MTLS_MDS_CERT_CHAIN_AND_KEY_PATH); | ||
| if (rootFile.isFile() && certKeyFile.isFile()) { | ||
| // Try to connect to S2A using mTLS. | ||
| ChannelCredentials mtlsToS2AChannelCredentials = null; | ||
| try { | ||
| mtlsToS2AChannelCredentials = | ||
| createMtlsToS2AChannelCredentials(rootFile, certKeyFile, certKeyFile); | ||
| } catch (IOException ignore) { | ||
| // Fallback to plaintext-to-S2A connection on error. | ||
| LOG.log( | ||
| Level.WARNING, | ||
| "Cannot establish an mTLS connection to S2A due to error creating MTLS to MDS TlsChannelCredentials credentials, falling back to plaintext connection to S2A: " | ||
| + ignore.getMessage()); | ||
| return createPlaintextToS2AChannelCredentials(plaintextAddress); | ||
| if (s2aChannelCredentialsObject == null) { | ||
lqiu96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // s2aChannelCredentialsObject is initialized once and shared by all instances of the class. | ||
| // To prevent a race on initialization, the object initialization is synchronized on the class | ||
| // object. | ||
| synchronized (InstantiatingGrpcChannelProvider.class) { | ||
lqiu96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (s2aChannelCredentialsObject != null) { | ||
| return s2aChannelCredentialsObject; | ||
| } | ||
| SecureSessionAgentConfig config = s2aConfigProvider.getConfig(); | ||
| String plaintextAddress = config.getPlaintextAddress(); | ||
| String mtlsAddress = config.getMtlsAddress(); | ||
| if (Strings.isNullOrEmpty(mtlsAddress)) { | ||
| // Fallback to plaintext connection to S2A. | ||
| LOG.log( | ||
| Level.INFO, | ||
| "Cannot establish an mTLS connection to S2A because autoconfig endpoint did not return a mtls address to reach S2A."); | ||
| s2aChannelCredentialsObject = createPlaintextToS2AChannelCredentials(plaintextAddress); | ||
| return s2aChannelCredentialsObject; | ||
| } | ||
| // Currently, MTLS to MDS is only available on GCE. See: | ||
| // https://cloud.google.com/compute/docs/metadata/overview#https-mds | ||
| // Try to load MTLS-MDS creds. | ||
| File rootFile = new File(MTLS_MDS_ROOT_PATH); | ||
| File certKeyFile = new File(MTLS_MDS_CERT_CHAIN_AND_KEY_PATH); | ||
| if (rootFile.isFile() && certKeyFile.isFile()) { | ||
| // Try to connect to S2A using mTLS. | ||
| ChannelCredentials mtlsToS2AChannelCredentials = null; | ||
| try { | ||
| mtlsToS2AChannelCredentials = | ||
| createMtlsToS2AChannelCredentials(rootFile, certKeyFile, certKeyFile); | ||
| } catch (IOException ignore) { | ||
| // Fallback to plaintext-to-S2A connection on error. | ||
| LOG.log( | ||
| Level.WARNING, | ||
| "Cannot establish an mTLS connection to S2A due to error creating MTLS to MDS TlsChannelCredentials credentials, falling back to plaintext connection to S2A: " | ||
| + ignore.getMessage()); | ||
| s2aChannelCredentialsObject = createPlaintextToS2AChannelCredentials(plaintextAddress); | ||
| return s2aChannelCredentialsObject; | ||
| } | ||
| s2aChannelCredentialsObject = | ||
| buildS2AChannelCredentials(mtlsAddress, mtlsToS2AChannelCredentials); | ||
| return s2aChannelCredentialsObject; | ||
| } else { | ||
| // Fallback to plaintext-to-S2A connection if MTLS-MDS creds do not exist. | ||
| LOG.log( | ||
| Level.INFO, | ||
| "Cannot establish an mTLS connection to S2A because MTLS to MDS credentials do not" | ||
| + " exist on filesystem, falling back to plaintext connection to S2A"); | ||
| s2aChannelCredentialsObject = createPlaintextToS2AChannelCredentials(plaintextAddress); | ||
| return s2aChannelCredentialsObject; | ||
| } | ||
| } | ||
| return buildS2AChannelCredentials(mtlsAddress, mtlsToS2AChannelCredentials); | ||
| } else { | ||
| // Fallback to plaintext-to-S2A connection if MTLS-MDS creds do not exist. | ||
| LOG.log( | ||
| Level.INFO, | ||
| "Cannot establish an mTLS connection to S2A because MTLS to MDS credentials do not exist on filesystem, falling back to plaintext connection to S2A"); | ||
| return createPlaintextToS2AChannelCredentials(plaintextAddress); | ||
| } | ||
| return s2aChannelCredentialsObject; | ||
| } | ||
|
|
||
| private ManagedChannel createSingleChannel() throws IOException { | ||
|
|
||
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.
nit: maybe add a comment here that we are using this single credentials object?
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.
Good call, added a comment about this.