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

feat: support self-signed JWT flow for service accounts #774

Merged
merged 15 commits into from
Apr 21, 2021

Conversation

busunkim96
Copy link
Contributor

@busunkim96 busunkim96 commented Feb 10, 2021

See RFC (internal only) and https://aip.dev/auth/4111.

Support the self-signed JWT flow for service accounts by passing default_scopes and default_host in calls to the auth library and create_channel. This depends on features exposed in the following PRs: googleapis/python-api-core#134, googleapis/google-auth-library-python#665.

It may be easier to look at https://github.com/googleapis/python-translate/pull/107/files for a diff on a real library.

This change is written so that the library is (temporarily) compatible with older google-api-core and google-auth versions. Because of this it not possible to reach 100% coverage on a single unit test run. pytest runs twice in two of the nox sessions.

Miscellaneous changes:

  • sprinkled in __init__.py files in subdirs of the test/ directory, as otherwise pytest-cov seems to fail to collect coverage properly in some instances.
  • new dependency on packaging for Version comparison https://pypi.org/project/packaging/

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

codecov bot commented Feb 10, 2021

Codecov Report

Merging #774 (93ffdd2) into master (7c185e8) will not change coverage.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff            @@
##            master      #774   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           26        27    +1     
  Lines         1608      1653   +45     
  Branches       328       337    +9     
=========================================
+ Hits          1608      1653   +45     
Impacted Files Coverage Δ
gapic/schema/metadata.py 100.00% <ø> (ø)
gapic/utils/case.py 100.00% <ø> (ø)
gapic/utils/reserved_names.py 100.00% <ø> (ø)
gapic/generator/generator.py 100.00% <100.00%> (ø)
gapic/schema/api.py 100.00% <100.00%> (ø)
gapic/schema/wrappers.py 100.00% <100.00%> (ø)
gapic/utils/__init__.py 100.00% <100.00%> (ø)
gapic/utils/checks.py 100.00% <100.00%> (ø)
gapic/utils/options.py 100.00% <100.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 7ca9222...93ffdd2. Read the comment docs.

Comment on lines 101 to 102
if host != self.DEFAULT_HOST and not scopes:
scopes = self.AUTH_SCOPES
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If port is provided (say pubsub.googleapis.com:443 instead of pubsub.googleapis.com) the library will think the OAuth2 flow is necessary.

I initially tried splitting on : but that broke the showcase tests where default host is localhost. Is there a better way to parse the provided host reliably?

Copy link
Contributor

@software-dov software-dov Feb 24, 2021

Choose a reason for hiding this comment

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

Silly questions: which is the OAuth2 flow, and why do we not want it all the time? Is it the "old style"? What are we trying to parse out? We can always fall back to regexes, something like

host_re = re.compile("(?P<host>[^:]+)(:(?P<port>\d+))?")

Or is that not the part of parsing out the host that's problematic?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah sorry for being vague.

The OAuth2 flow (currently always used for service accounts) requires a call out to oauth2.googleapis.com. With a service account, it's sometimes possible to "self-sign" the token, which avoids a network round trip.

The self-signed JWT option can be used when:

  1. A service account is being used
  2. A scope has not been set by the user
  3. A target_audience has not been set by the user (irrelevant as we don't have an client option for this)
  4. An api_endpoint has not been set by the user

Number 4 is what's relevant here. If the user is using the non-default endpoint we want to make sure the auth library uses OAuth2. The suggested way to do this in the doc is to pass user scopes to the auth library.

I think that regex will work! I'll experiment and push a new commit.

@busunkim96 busunkim96 marked this pull request as ready for review February 24, 2021 21:39
Copy link
Contributor

@software-dov software-dov left a comment

Choose a reason for hiding this comment

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

Wow, this is quite the endeavor. Thanks for doing it!

Comment on lines 101 to 102
if host != self.DEFAULT_HOST and not scopes:
scopes = self.AUTH_SCOPES
Copy link
Contributor

@software-dov software-dov Feb 24, 2021

Choose a reason for hiding this comment

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

Silly questions: which is the OAuth2 flow, and why do we not want it all the time? Is it the "old style"? What are we trying to parse out? We can always fall back to regexes, something like

host_re = re.compile("(?P<host>[^:]+)(:(?P<port>\d+))?")

Or is that not the part of parsing out the host that's problematic?

@arithmetic1728
Copy link
Collaborator

arithmetic1728 commented Feb 25, 2021

FYI, cl/357773081 is submitted, so I guess custom endpoint will no longer be an issue for self signed JWT going forward. @bshaffer

@busunkim96
Copy link
Contributor Author

@bshaffer Would you be able to take a look?

@bshaffer
Copy link
Contributor

bshaffer commented Mar 8, 2021

FYI, cl/357773081 is submitted, so I guess custom endpoint will no longer be an issue for self signed JWT going forward. @bshaffer

This is true but it still will be some time before it rolls out. It's up to the owners of this repo if that's acceptable to wait for the rollout or go ahead with the fix we have here. I'd prefer the latter.

@arithmetic1728
Copy link
Collaborator

FYI, cl/357773081 is submitted, so I guess custom endpoint will no longer be an issue for self signed JWT going forward. @bshaffer

This is true but it still will be some time before it rolls out. It's up to the owners of this repo if that's acceptable to wait for the rollout or go ahead with the fix we have here. I'd prefer the latter.

Yep, agreed, it is just a FYI. It still takes one or two weeks for the roll out.

@busunkim96
Copy link
Contributor Author

@bshaffer PTAL. :)

@software-dov
Copy link
Contributor

Does @bshaffer need to take a look, or is this good to merge?

@busunkim96
Copy link
Contributor Author

@bshaffer or @arithmetic1728 would you mind taking another look?

Copy link
Contributor Author

@busunkim96 busunkim96 left a comment

Choose a reason for hiding this comment

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

@bshaffer I removed the scope override with custom endpoints to match guidance in https://docs.google.com/document/d/1FoJPYQu8ZHdGBFY9TBRZCDrWJJnxw3fAz5CG5mdZR9E/edit. Could you take a look?

@busunkim96
Copy link
Contributor Author

@software-dov Could you take one more look? Thank you for your help getting this over the line. :)

Copy link
Contributor

@bshaffer bshaffer left a comment

Choose a reason for hiding this comment

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

One question I still have is, unfortunately, for the case of the regional endpoint us-documentai.googleapis.com, we need a check to make sure the DEFAULT_HOST doesn't contain a regional endpoint.

If this is something we should do here, or somewhere else, I am not sure, I just want to verify it's being considered!

@@ -99,13 +99,13 @@ class {{ service.name }}Transport(abc.ABC):
scopes_kwargs = self._get_scopes_kwargs(self._host, scopes)

# Save the scopes.
self._scopes = scopes_kwargs["scopes"]
self._scopes = scopes or self.AUTH_SCOPES
Copy link
Contributor

Choose a reason for hiding this comment

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

If I'm understanding this correctly, scopes_kwargs["scopes"] was essentially scopes but with a check for if the endpoint was supplied. And I am interpreting scopes or self.AUTH_SCOPES to mean "user supplied scopes or the default scopes"

Is that accurate?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep! Since the endpoint restriction has been lifted I went back to "user supplied scopes or the default scopes"

@@ -120,19 +120,7 @@ class {{ service.name }}Transport(abc.ABC):
self._credentials = credentials


@classmethod
def _get_user_scopes(cls, host: str, scopes: Optional[Sequence[str]]) -> Optional[Sequence[str]]:
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@busunkim96
Copy link
Contributor Author

One question I still have is, unfortunately, for the case of the regional endpoint us-documentai.googleapis.com, we need a check to make sure the DEFAULT_HOST doesn't contain a regional endpoint.

@bshaffer From reading the doc it seems like documentai is the exception, so I am planning on handling that with a synthtool/owlbot replace. If we anticipate more APIs using the regional endpoints as the default host I can build logic into the generator.

@bshaffer
Copy link
Contributor

bshaffer commented Apr 16, 2021

That sounds great!

@busunkim96 busunkim96 merged commit 89d6f35 into master Apr 21, 2021
@busunkim96 busunkim96 deleted the self-signed-jwt branch April 21, 2021 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes This human has signed the Contributor License Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants