-
Notifications
You must be signed in to change notification settings - Fork 254
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: Add AWS Workload Identity Federation Support #386
feat: Add AWS Workload Identity Federation Support #386
Conversation
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
9e712c5
to
c294764
Compare
Any updates on this? |
I'm still tracking this thread and willing to make any changes required to get it merged. For now I've just been using it by overriding my Gemfile to point at my fork of the code, although I'd suggest you make your own fork with this copy of the code if you take that route since my branch could change at any time. |
Hi @rbclark |
@bajajneha27 Not a problem, thanks! Let me know if there's anything else that needs to be done on my end! |
@bajajneha27 checking in whether the review is still in progress. Or if it would help giving this higher priority if we talk to our GCP reps. Thanks! |
Hi @hanikesn |
lib/googleauth/external_account.rb
Outdated
# This module handles the retrieval of credentials from Google Cloud | ||
# by utilizing the AWS EC2 metadata service and then exchanging the | ||
# credentials for a short-lived Google Cloud access token. | ||
class AwsCredentials |
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.
I think it would make sense to move the AWS specific code to a separate file. It's very likely that the ExternalAccount credentials will also be extended to handle File based external account credentials, URL based external account credentials, and executable external account credentials, and this file will quickly become unmaintable
lib/googleauth/external_account.rb
Outdated
raise "a json file is required for external account credentials" unless json_key_io | ||
user_creds = read_json_key json_key_io | ||
|
||
AwsCredentials.new( |
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.
It seems like there's some tight coupling between this class and the AWS class. It might make sense to move some of the amazon specific logic into the AwsCredentials class
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.
I've gone ahead and moved the AWS region information into the AWS class as described below. I think the rest of these are somewhat generic based on the reference code I used from the other google auth libraries but I am still open to suggestions (python example)
lib/googleauth/external_account.rb
Outdated
token_url: user_creds["token_url"], | ||
credential_source: user_creds["credential_source"], | ||
service_account_impersonation_url: user_creds["service_account_impersonation_url"], | ||
region: ENV[CredentialsLoader::AWS_REGION_VAR] || ENV[CredentialsLoader::AWS_DEFAULT_REGION_VAR] |
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.
Wouldn't this make more sense to move into the region
method of AwsCredentials?
lib/googleauth/external_account.rb
Outdated
|
||
@environment_id = @credential_source["environment_id"] | ||
@region_url = @credential_source["region_url"] | ||
@credential_source_url = @credential_source["url"] |
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.
I think the purpose of this variable is more clear as @cred_verification_url
lib/googleauth/external_account.rb
Outdated
end | ||
|
||
def get_impersonated_access_token token, options = {} | ||
c = options[:connection] || Faraday.default_connection |
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.
You use options[:connection] || Faraday.default_connection
a lot. Move this to a new connection
method, and then you can just cay connection.post
or connection.get
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.
I did this to try to match the rest of the application in the hopes that in the future it could be changed all at once. I've gone ahead and moved this into a helper now and tried to keep it generic so that the other classes in this library which have calls to options[:connection]
can reuse it as well. One thing that would likely make this easier in this codebase at least would be if the options were set at a class level instead of being passed into each individual method.
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.
Hmmm, yeah, I agree. I'll have to talk with the rest of the team to see if we can refactor some of these
# @param [String] token_exchange_endpoint | ||
# The token exchange endpoint. | ||
def initialize options = {} | ||
@token_exchange_endpoint = options[:token_exchange_endpoint] |
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.
Maybe we should raise an error if there's no token exchange endpoint provided, as the class won't work otherwise
|
||
c = options[:connection] || Faraday.default_connection | ||
|
||
headers = URLENCODED_HEADERS.dup.merge(options[:additional_headers] || {}) |
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.
Could we either add the ability to add authentication to the headers, or a TODO reminder that this will need to be done?
lib/googleauth/external_account.rb
Outdated
notify_refresh_listeners | ||
end | ||
|
||
def notify_refresh_listeners |
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.
Isn't this method the same as the one defined in the client?
lib/googleauth/base_client.rb
Outdated
@@ -0,0 +1,76 @@ | |||
# Copyright 2015 Google, Inc. |
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.
2022
# BaseClient is a class used to contain common methods that are required by any | ||
# Credentials Client, including AwsCredentials, ServiceAccountCredentials, | ||
# and UserRefreshCredentials. | ||
module BaseClient |
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.
Do we need this module? Is there a reason that ExternalAccountCredentials can't extend from Signet::Oauth2::Client like the other credential classes in this library, instead of selecting a few of the methods from this module, and creating a superclass for it? It seems like it would just be easier to overwrite the methods from the client that don't work
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 thinking here was that the ExternalAccountCredentials uses a completely different OAuth2 flow that is not supported by Signet. Since Signet doesn't support token exchange I figured it would just add confusion for other developers if I pulled in Signet and didn't use any of it's features. The current implementation is the most clear way i could think of to avoid confusion around what Signet does and doesn't do while still DRYing everything up. If you would prefer I just paste everything into the Signet file and include it everywhere I can go ahead and make that change and try it out, just figured I would explain my thinking and get your feedback before making the change.
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.
I think this makes sense. Let's mention Signet::Oauth2::Client in the comments, and mention that this has been created as a superclass for Signet for Credentials that don't need all of Signet's features
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.
Is this more reasonable? a89e880
00ca080
to
4695994
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.
Thank you for reviewing this! I believe I have corrected everything you noted except for the moving of everything into signet.rb
. If after my explanation of my thought process there you still want that moved over please let me know and I will do so!
lib/googleauth/external_account.rb
Outdated
end | ||
|
||
def get_impersonated_access_token token, options = {} | ||
c = options[:connection] || Faraday.default_connection |
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.
I did this to try to match the rest of the application in the hopes that in the future it could be changed all at once. I've gone ahead and moved this into a helper now and tried to keep it generic so that the other classes in this library which have calls to options[:connection]
can reuse it as well. One thing that would likely make this easier in this codebase at least would be if the options were set at a class level instead of being passed into each individual method.
lib/googleauth/external_account.rb
Outdated
raise "a json file is required for external account credentials" unless json_key_io | ||
user_creds = read_json_key json_key_io | ||
|
||
AwsCredentials.new( |
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.
I've gone ahead and moved the AWS region information into the AWS class as described below. I think the rest of these are somewhat generic based on the reference code I used from the other google auth libraries but I am still open to suggestions (python example)
# BaseClient is a class used to contain common methods that are required by any | ||
# Credentials Client, including AwsCredentials, ServiceAccountCredentials, | ||
# and UserRefreshCredentials. | ||
module BaseClient |
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 thinking here was that the ExternalAccountCredentials uses a completely different OAuth2 flow that is not supported by Signet. Since Signet doesn't support token exchange I figured it would just add confusion for other developers if I pulled in Signet and didn't use any of it's features. The current implementation is the most clear way i could think of to avoid confusion around what Signet does and doesn't do while still DRYing everything up. If you would prefer I just paste everything into the Signet file and include it everywhere I can go ahead and make that change and try it out, just figured I would explain my thinking and get your feedback before making the change.
Any updates on when this will be available? |
hi @ScruffyProdigy would you review the updates? |
…impersonated credential
…st compatible with apply_auth_examples. ExternalAccount was not fully compatible with the other providers, this brings it in line and moves some common methods between the other provides and ExternalAccount into a module which is now included by the AWSClient provider and Signet::Auth::Client
…external account, create dedicated Connection helper module, other misc PR cleanup
4695994
to
ce07c11
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.
This is looking good!
raise "a json file is required for external account credentials" unless json_key_io | ||
user_creds = read_json_key json_key_io | ||
|
||
Google::Auth::ExternalAccount::AwsCredentials.new( |
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.
There are a few different types of external account credential files that exist right now. Let's make sure the file is intended to be AWS credentials before we use that class to create them, and raise an "unknown credential" error until we've implemented them. Use either of these methods:
In GoLang, we make sure that the environment ID of the credential source is "AWS1" before we create AWS credentials (and could be AWS2, AWS3, etc for future versions of these credentials)
In Python, we make sure that the subject token type of the credential source is "urn:ietf:params:aws:token-type:aws4_request"
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.
Fixed in 994456a
# Signet::OAuth2::Client creates an OAuth2 client | ||
# | ||
# This reopens Client to add #apply and #apply! methods which update a | ||
# hash with the fetched authentication token. | ||
class Client | ||
include Google::Auth::BaseClient | ||
|
||
def configure_connection options |
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.
Would something like this in the base_client make it easier to not need to pass around the options for the connection?
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.
The biggest issue I see is that configure_connection
returns self
and not a Faraday::Connection
. I think the core issue is that signet does this internally (https://github.com/googleapis/signet/blob/main/lib/signet/oauth_2/client.rb#L993) and so the code here seems to just be setting up for handing off to signet. The code for external accounts on the other hand needs to actually handle the default case as well and is doing everything instead of just handing off to signet.
994456a
to
94e1935
Compare
@ScruffyProdigy Sorry I had to correct one line too long error, could you re-approve the workflow run? |
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 adding this @rbclark 🙌
Note this is missing a lot of tests. See the test coverage in the AWS portion of the NodeJS WIF PR.
Google::Auth::ExternalAccount::AwsCredentials.new( | ||
audience: user_creds["audience"], | ||
scope: scope, | ||
subject_token_type: user_creds["subject_token_type"], |
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.
The token_url and service_account_impersonation_url need to be validated.
See the java implementation: https://github.com/googleapis/google-auth-library-java/blob/main/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java#L219
@credential_source = options[:credential_source] || {} | ||
@service_account_impersonation_url = options[:service_account_impersonation_url] | ||
@environment_id = @credential_source["environment_id"] | ||
@region_url = @credential_source["region_url"] |
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.
AWS specific URLs should be validated, see googleapis/google-auth-library-java#1079
} | ||
end | ||
|
||
role_name = fetch_metadata_role_name options |
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.
We need to support IMDSv2, see https://github.com/googleapis/google-auth-library-java/pull/850/files.
We've already covered this in PR#418. |
I wouldn't call it "already", but good news, thanks! |
This adds AWS Workload Identity Federation support for AWS EC2 -> Google Cloud.
I understand this PR is likely not in it's final stage, but I was hoping to get some feedback on it from the maintainers on what would still be necessary for it to be fully acceptable.
Outstanding Questions:
lib/googleauth/credentials_loader.rb:24:5: C: Metrics/ModuleLength: Module has too many lines. [113/110]
, considering the nature of this file, would it be acceptable to ignore this finding?Closes #354