Skip to content
This repository has been archived by the owner on Sep 26, 2023. It is now read-only.

feat: self signed jwt support #1294

Closed
wants to merge 6 commits into from
Closed

feat: self signed jwt support #1294

wants to merge 6 commits into from

Conversation

arithmetic1728
Copy link
Contributor

@arithmetic1728 arithmetic1728 commented Feb 8, 2021

This PR adds self signed jwt support (https://google.aip.dev/auth/4111). Googlers see this doc for more details.

If user uses ServiceAccountCredentials, and doesn't provide custom endpoint, scopes, and audience, then a new ServiceAccountJwtAccessCredentials should be created and used for self signed jwt feature.

This PR doesn't break existing client libraries.

1. Details

endpoint

In StubSettings.java, currently because both user and client library set the endpoint property, so we cannot tell whether the endpoint property is a custom endpoint set by user. To solve this problem, this PR creates a new defaultApiEndpoint property for the client libraries to use. Then we can compare endpoint with defaultApiEndpoint to know if endpoint is a custom endpoint.

Client library example PR using java-cloudbuild

Note: once cl/356594013 is checked in and rolled out, then all the changes in this section is not needed anymore. Depending on the actual timeline, I can update this PR or create a subsequent PR.

scopes

There are two types of CredentialsProvider for the user to provide a credentials: GoogleCredentialsProvider and FixedCredentialsProvider. Whether scopes are provided by user depends on the provider type.

GoogleCredentialsProvider provides ADC credential. Currently both user and client library use setScopesToApply method to set scopes. To tell the difference, this PR creates a new setDefaultScopes method for the client library to use. Client library example PR using java-cloudbuild

FixedCredentialsProvider wraps a credential created by the user. If the credential is ServiceAccountCredentials, then we can look at the scopes property.

audience

Users cannot set audience for ServiceAccountCredentials regardless of the credential provider type, so this doesn't apply.

2. Client examples PRs

This PR works for both GAPIC and DIREGAPIC clients. Examples PRs for client libraries:

  • java-cloudbuild (GAPIC): PR
  • java-compute (DIREGAPIC): PR

@google-cla google-cla bot added the cla: yes This human has signed the Contributor License Agreement. label Feb 8, 2021
@codecov
Copy link

codecov bot commented Feb 8, 2021

Codecov Report

Merging #1294 (72b2544) into master (721617b) will decrease coverage by 0.09%.
The diff coverage is 70.49%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master    #1294      +/-   ##
============================================
- Coverage     79.34%   79.24%   -0.10%     
- Complexity     1235     1245      +10     
============================================
  Files           209      209              
  Lines          5398     5455      +57     
  Branches        454      464      +10     
============================================
+ Hits           4283     4323      +40     
- Misses          936      945       +9     
- Partials        179      187       +8     
Impacted Files Coverage Δ Complexity Δ
...main/java/com/google/api/gax/rpc/StubSettings.java 73.88% <44.44%> (-1.80%) 13.00 <1.00> (+1.00) ⬇️
...ain/java/com/google/api/gax/rpc/ClientContext.java 85.24% <70.27%> (-6.62%) 22.00 <4.00> (+4.00) ⬇️
...google/api/gax/core/GoogleCredentialsProvider.java 87.80% <86.66%> (+1.59%) 11.00 <1.00> (+5.00)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 721617b...3fce95d. Read the comment docs.

@arithmetic1728 arithmetic1728 marked this pull request as ready for review February 10, 2021 07:58
@arithmetic1728 arithmetic1728 requested review from a team as code owners February 10, 2021 07:58
}

/**
* If user doesn't provide custom endpoint and scopes, and the credentials is service account
Copy link
Contributor

Choose a reason for hiding this comment

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

is --> are

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for all the comments. Will address them in the next commit.

/**
* If user doesn't provide custom endpoint and scopes, and the credentials is service account
* credentials, then we need to create a new ServiceAccountJwtAccessCredentials to use self signed
* jwt. See https://google.aip.dev/auth/4111.
Copy link
Contributor

Choose a reason for hiding this comment

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

JWT


/**
* If user doesn't provide custom endpoint and scopes, and the credentials is service account
* credentials, then we need to create a new ServiceAccountJwtAccessCredentials to use self signed
Copy link
Contributor

Choose a reason for hiding this comment

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

rewrite in third person
This doc comment only seems to describe one detail of the method's implementation, not the entire method.

@@ -134,9 +161,20 @@ public static Builder newBuilder() {
@BetaApi
public abstract List<String> getJwtEnabledScopes();

/**
* Default scopes is for client libraries to use. Users should use setScopesToApply to set
Copy link
Contributor

Choose a reason for hiding this comment

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

is --> are

@@ -134,9 +161,20 @@ public static Builder newBuilder() {
@BetaApi
public abstract List<String> getJwtEnabledScopes();

/**
* Default scopes is for client libraries to use. Users should use setScopesToApply to set
* scopes, or setJwtEnabledScopes to force to use ServiceAccountJWTCredentials.
Copy link
Contributor

Choose a reason for hiding this comment

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

force what?

@@ -132,6 +140,73 @@ public static ClientContext create(ClientSettings settings) throws IOException {
return create(settings.getStubSettings());
}

/**
* If user doesn't provide custom endpoint and scopes, and the credentials is service account
* credentials, then we need to create a new ServiceAccountJwtAccessCredentials to use self signed
Copy link
Contributor

Choose a reason for hiding this comment

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

avoid first person in doc comments, per google style

@@ -597,4 +602,70 @@ public void testUserAgentConcat() throws Exception {
assertThat(transportChannel.getHeaders())
.containsEntry("user-agent", "user-supplied-agent internal-agent");
}

@Test
public void testSelfSignedJwtEndpointNotDefault() throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

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

can exception be more specific?

// Test when endpoint is null.
Credentials returnedCredentials =
ClientContext.determineSelfSignedJWTCredentials(provider, null, "https://foo");
assertThat(returnedCredentials).isEqualTo(credentials);
Copy link
Contributor

Choose a reason for hiding this comment

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

unnecessary use of Truth for simple equality

}

@Test
public void testSelfSignedJwtWithGoogleCredentialsProvider() throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

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

can exception be more specific?

}

@Test
public void testSelfSignedJwtWithFixedCredentialsProvider() throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

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

can exception be more specific?

Copy link
Contributor

@igorbernstein2 igorbernstein2 left a comment

Choose a reason for hiding this comment

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

Hi, thanks for working on this. However I think there are a couple of flaws here:

  1. this breaks existing functionality of restricting access by scope
  2. it prevents usage of jwts custom endpoints: for example this will prevent bigtable dataflow connector from being able to access batch-bigtable.googleapis.com using JWTs (its the same OP service, with different routing rules).
    2b. audiences arent necessarily tied to the endpoint, it just happens that it aligns most of the time. Forcing the audience to be a prefix of the endpoint will prevent that usecase
  3. This prevents adding outgoing sidecar proxies in front of our clients.
  4. Given that this a new api, anything exposed as public should be marked as internal api or beta api in case it turns out that we need to change this because it doesnt account for everything that client devs or customers need.

@@ -134,9 +161,20 @@ public static Builder newBuilder() {
@BetaApi
public abstract List<String> getJwtEnabledScopes();
Copy link
Contributor

Choose a reason for hiding this comment

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

Please deprecate the previous approach of setJwtEnabledScopes, it's still marked as BetaApi and we shouldnt have 2 ways to opt into jwt

// provide scopes, then we create a ServiceAccountJwtAccessCredentials and use this credentials
// instead. This credential uses self signed jwt, and it is more efficient since the network
// call to token endpoint is avoided. See https://google.aip.dev/auth/4111.
if (provider instanceof GoogleCredentialsProvider) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can all of this move into a static helper in CredentialsProvider (or anything that is more related to credentials)? Having the credential logic split between CredentialsProviders and here makes it very hard to follow the logic unless you know what you are looking for

* credentials, then we need to create a new ServiceAccountJwtAccessCredentials to use self signed
* jwt. See https://google.aip.dev/auth/4111.
*/
public Credentials getCredentials(boolean endpointIsDefault, URI audienceForSelfSignedJwt)
Copy link
Contributor

Choose a reason for hiding this comment

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

Please mark audience as @Nullable

if (!scopes.contains(scope)) {
scopes.add(scope);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This breaks an existing usecase of using a narrow scope to restrict functionality. For example if a customer wanted to restrict bigtable access in an application to just read only, they would change the scopes to https://www.googleapis.com/auth/cloud-bigtable.data.readonly.

And if this is an intentional breakage, this needs to be made a lot more apparent in a separate PR that explicitly changes that behavior

@@ -120,6 +123,10 @@ public final String getEndpoint() {
return endpoint;
}

public final String getDefaultApiEndpoint() {
Copy link
Contributor

Choose a reason for hiding this comment

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

should this be public? it seems like this entire api is designed for client authors, I dont think this should be made public to end users

@arithmetic1728 arithmetic1728 marked this pull request as draft February 11, 2021 01:13
@arithmetic1728
Copy link
Contributor Author

Hi, thanks for working on this. However I think there are a couple of flaws here:

  1. this breaks existing functionality of restricting access by scope

Yes, this is a mistake.

  1. it prevents usage of jwts custom endpoints: for example this will prevent bigtable dataflow connector from being able to access batch-bigtable.googleapis.com using JWTs (its the same OP service, with different routing rules).

Yes, I was waiting for cl/356594013 to unblock custom endpoints. It seems cl will be submitted soon, I will change the design.

2b. audiences arent necessarily tied to the endpoint, it just happens that it aligns most of the time. Forcing the audience to be a prefix of the endpoint will prevent that usecase

I guess I can change the design to use the prefix of endpoint, only if prefix of endpoint == prefix of audiences. For http client like java-compute, only the prefix of endpoint is a valid audience.

  1. This prevents adding outgoing sidecar proxies in front of our clients.

How does this design prevent it?

  1. Given that this a new api, anything exposed as public should be marked as internal api or beta api in case it turns out that we need to change this because it doesnt account for everything that client devs or customers need.

Yep.

@igorbernstein2
Copy link
Contributor

  1. This prevents adding outgoing sidecar proxies in front of our clients.

The problem is that you are tying the audience to the endpoint. Example scenario: bigtable client with a caching read through forward proxy. The proxy will forward the request to bigtable if the response is not cached. In this case, the endpoint is the proxy, while the audience of the creds is bigtable.googleapis.com.

@arithmetic1728
Copy link
Contributor Author

  1. This prevents adding outgoing sidecar proxies in front of our clients.

The problem is that you are tying the audience to the endpoint. Example scenario: bigtable client with a caching read through forward proxy. The proxy will forward the request to bigtable if the response is not cached. In this case, the endpoint is the proxy, while the audience of the creds is bigtable.googleapis.com.

The audience being set is a default audience, which is not used if uri is provided when creating the token. Currently for ServiceAccountJWTCredentials, if uri and default audience are both not set, program will throw exception. Since the jwt credentials set by gax-java via jwt enabled scopes doesn't have default audience, so I think this scenario uri must be already set, so it won't be affected.

@arithmetic1728
Copy link
Contributor Author

I changed the design and now the major implementation is in auth library. On one hand, cl/356594013 is submitted so we no longer need to worry about if the endpoint is default or not, which makes it easy to implement the feature in auth lib; on the other hand, the changes to gax-java is now minimal and shouldn't affect any existing client libs.

Please check out this PR in auth lib and this PR in gax-java. Thanks! @elharo @igorbernstein2

@chanseokoh chanseokoh deleted the jwt branch October 29, 2021 22:11
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
cla: yes This human has signed the Contributor License Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants