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: Include universe_domain in credentials #460

Merged
merged 1 commit into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions lib/googleauth/compute_engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def reset_cache
# Overrides the super class method to change how access tokens are
# fetched.
def fetch_access_token _options = {}
if target_audience
if token_type == :id_token
query = { "audience" => target_audience, "format" => "full" }
entry = "service-accounts/default/identity"
else
Expand Down Expand Up @@ -113,12 +113,16 @@ def fetch_access_token _options = {}
private

def build_token_hash body, content_type
if ["text/html", "application/text"].include? content_type
key = target_audience ? "id_token" : "access_token"
{ key => body }
else
Signet::OAuth2.parse_credentials body, content_type
end
hash =
if ["text/html", "application/text"].include? content_type
{ token_type.to_s => body }
else
Signet::OAuth2.parse_credentials body, content_type
end
universe_domain = Google::Cloud.env.lookup_metadata "universe", "universe_domain"
universe_domain = "googleapis.com" if !universe_domain || universe_domain.empty?
hash["universe_domain"] = universe_domain.strip
hash
end
end
end
Expand Down
19 changes: 13 additions & 6 deletions lib/googleauth/credentials.rb
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ def self.paths= new_paths
# @return [Object] The value
#
def self.lookup_auth_param name, method_name = name
val = instance_variable_get "@#{name}".to_sym
val = instance_variable_get :"@#{name}"
val = yield if val.nil? && block_given?
return val unless val.nil?
return superclass.send method_name if superclass.respond_to? method_name
Expand Down Expand Up @@ -328,9 +328,13 @@ def self.lookup_local_constant name
# @return [Proc] Returns a reference to the {Signet::OAuth2::Client#apply} method,
# suitable for passing as a closure.
#
# @!attribute [rw] universe_domain
# @return [String] The universe domain issuing these credentials.
#
def_delegators :@client,
:token_credential_uri, :audience,
:scope, :issuer, :signing_key, :updater_proc, :target_audience
:scope, :issuer, :signing_key, :updater_proc, :target_audience,
:universe_domain, :universe_domain=

##
# Creates a new Credentials instance with the provided auth credentials, and with the default
Expand Down Expand Up @@ -506,12 +510,15 @@ def client_options options

needs_scope = options["target_audience"].nil?
# client options for initializing signet client
{ token_credential_uri: options["token_credential_uri"],
{
token_credential_uri: options["token_credential_uri"],
audience: options["audience"],
scope: (needs_scope ? Array(options["scope"]) : nil),
target_audience: options["target_audience"],
issuer: options["client_email"],
signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) }
signing_key: OpenSSL::PKey::RSA.new(options["private_key"]),
universe_domain: options["universe_domain"] || "googleapis.com"
}
end

# rubocop:enable Metrics/AbcSize
Expand All @@ -526,7 +533,7 @@ def update_from_hash hash, options
hash = stringify_hash_keys hash
hash["scope"] ||= options[:scope]
hash["target_audience"] ||= options[:target_audience]
@project_id ||= (hash["project_id"] || hash["project"])
@project_id ||= hash["project_id"] || hash["project"]
@quota_project_id ||= hash["quota_project_id"]
@client = init_client hash, options
end
Expand All @@ -536,7 +543,7 @@ def update_from_filepath path, options
json = JSON.parse ::File.read(path)
json["scope"] ||= options[:scope]
json["target_audience"] ||= options[:target_audience]
@project_id ||= (json["project_id"] || json["project"])
@project_id ||= json["project_id"] || json["project"]
@quota_project_id ||= json["quota_project_id"]
@client = init_client json, options
end
Expand Down
3 changes: 2 additions & 1 deletion lib/googleauth/external_account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ def make_aws_credentials user_creds, scope
subject_token_type: user_creds[:subject_token_type],
token_url: user_creds[:token_url],
credential_source: user_creds[:credential_source],
service_account_impersonation_url: user_creds[:service_account_impersonation_url]
service_account_impersonation_url: user_creds[:service_account_impersonation_url],
universe_domain: user_creds[:universe_domain]
)
end

Expand Down
2 changes: 2 additions & 0 deletions lib/googleauth/external_account/base_credentials.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ module BaseCredentials

attr_reader :expires_at
attr_accessor :access_token
attr_accessor :universe_domain

def expires_within? seconds
# This method is needed for BaseClient
Expand Down Expand Up @@ -110,6 +111,7 @@ def base_setup options
@quota_project_id = options[:quota_project_id]
@project_id = nil
@workforce_pool_user_project = options[:workforce_pool_user_project]
@universe_domain = options[:universe_domain] || "googleapis.com"

@expires_at = nil
@access_token = nil
Expand Down
3 changes: 2 additions & 1 deletion lib/googleauth/json_key_reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ def read_json_key json_key_io
json_key["private_key"],
json_key["client_email"],
json_key["project_id"],
json_key["quota_project_id"]
json_key["quota_project_id"],
json_key["universe_domain"]
]
end
end
Expand Down
16 changes: 11 additions & 5 deletions lib/googleauth/service_account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,13 @@ def self.make_creds options = {}
raise ArgumentError, "Cannot specify both scope and target_audience" if scope && target_audience

if json_key_io
private_key, client_email, project_id, quota_project_id = read_json_key json_key_io
private_key, client_email, project_id, quota_project_id, universe_domain = read_json_key json_key_io
else
private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR]
client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
quota_project_id = nil
universe_domain = nil
end
project_id ||= CredentialsLoader.load_gcloud_project_id

Expand All @@ -70,7 +71,8 @@ def self.make_creds options = {}
issuer: client_email,
signing_key: OpenSSL::PKey::RSA.new(private_key),
project_id: project_id,
quota_project_id: quota_project_id)
quota_project_id: quota_project_id,
universe_domain: universe_domain || "googleapis.com")
.configure_connection(options)
end

Expand All @@ -95,8 +97,9 @@ def initialize options = {}
def apply! a_hash, opts = {}
# Use a self-singed JWT if there's no information that can be used to
# obtain an OAuth token, OR if there are scopes but also an assertion
# that they are default scopes that shouldn't be used to fetch a token.
if target_audience.nil? && (scope.nil? || enable_self_signed_jwt?)
# that they are default scopes that shouldn't be used to fetch a token,
# OR we are not in the default universe and thus OAuth isn't supported.
if target_audience.nil? && (scope.nil? || enable_self_signed_jwt? || universe_domain != "googleapis.com")
apply_self_signed_jwt! a_hash
else
super
Expand Down Expand Up @@ -138,6 +141,7 @@ class ServiceAccountJwtHeaderCredentials
extend JsonKeyReader
attr_reader :project_id
attr_reader :quota_project_id
attr_accessor :universe_domain

# Create a ServiceAccountJwtHeaderCredentials.
#
Expand All @@ -154,14 +158,16 @@ def self.make_creds options = {}
def initialize options = {}
json_key_io = options[:json_key_io]
if json_key_io
@private_key, @issuer, @project_id, @quota_project_id =
@private_key, @issuer, @project_id, @quota_project_id, @universe_domain =
self.class.read_json_key json_key_io
else
@private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR]
@issuer = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
@project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
@quota_project_id = nil
@universe_domain = nil
end
@universe_domain ||= "googleapis.com"
@project_id ||= CredentialsLoader.load_gcloud_project_id
@signing_key = OpenSSL::PKey::RSA.new @private_key
@scope = options[:scope]
Expand Down
12 changes: 12 additions & 0 deletions lib/googleauth/signet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ module OAuth2
class Client
include Google::Auth::BaseClient

alias update_token_signet_base update_token!

def update_token! options = {}
options = deep_hash_normalize options
update_token_signet_base options
self.universe_domain = options[:universe_domain] if options.key? :universe_domain
self
end

def configure_connection options
@connection_info =
options[:connection_builder] || options[:default_connection]
Expand All @@ -36,6 +45,9 @@ def token_type
target_audience ? :id_token : :access_token
end

# Set the universe domain
attr_accessor :universe_domain

alias orig_fetch_access_token! fetch_access_token!
def fetch_access_token! options = {}
unless options[:connection]
Expand Down
6 changes: 4 additions & 2 deletions lib/googleauth/user_refresh.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,17 @@ def self.make_creds options = {}
"client_secret" => ENV[CredentialsLoader::CLIENT_SECRET_VAR],
"refresh_token" => ENV[CredentialsLoader::REFRESH_TOKEN_VAR],
"project_id" => ENV[CredentialsLoader::PROJECT_ID_VAR],
"quota_project_id" => nil
"quota_project_id" => nil,
"universe_domain" => nil
}
new(token_credential_uri: TOKEN_CRED_URI,
client_id: user_creds["client_id"],
client_secret: user_creds["client_secret"],
refresh_token: user_creds["refresh_token"],
project_id: user_creds["project_id"],
quota_project_id: user_creds["quota_project_id"],
scope: scope)
scope: scope,
universe_domain: user_creds["universe_domain"] || "googleapis.com")
.configure_connection(options)
end

Expand Down
56 changes: 53 additions & 3 deletions spec/googleauth/compute_engine_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@
end

def make_auth_stubs opts
universe_stub = stub_request(:get, "http://169.254.169.254/computeMetadata/v1/universe/universe_domain")
.with(headers: { "Metadata-Flavor" => "Google" })
if !defined?(@universe_domain) || !@universe_domain
universe_stub.to_return body: "", status: 404, headers: {"Metadata-Flavor" => "Google" }
elsif @universe_domain.is_a? Class
universe_stub.to_raise @universe_domain
else
universe_stub.to_return body: @universe_domain, status: 200, headers: {"Metadata-Flavor" => "Google" }
end
if opts[:access_token]
body = MultiJson.dump("access_token" => opts[:access_token],
"token_type" => "Bearer",
Expand All @@ -50,17 +59,58 @@ def make_auth_stubs opts
.with(headers: { "Metadata-Flavor" => "Google" })
.to_return(body: body,
status: 200,
headers: { "Content-Type" => "application/json" })
headers: { "Content-Type" => "application/json", "Metadata-Flavor" => "Google" })
elsif opts[:id_token]
stub_request(:get, MD_ID_URI)
.with(headers: { "Metadata-Flavor" => "Google" })
.to_return(body: opts[:id_token],
status: 200,
headers: { "Content-Type" => "text/html" })
headers: { "Content-Type" => "text/html", "Metadata-Flavor" => "Google" })
end
end

context "default universe" do
it_behaves_like "apply/apply! are OK"

it "sets the universe" do
make_auth_stubs access_token: "1/abcde"
@client.fetch_access_token!
expect(@client.universe_domain).to eq("googleapis.com")
end
end

it_behaves_like "apply/apply! are OK"
context "custom universe" do
before :example do
@universe_domain = "myuniverse.com"
end

it_behaves_like "apply/apply! are OK"

it "sets the universe" do
make_auth_stubs access_token: "1/abcde"
@client.fetch_access_token!
expect(@client.universe_domain).to eq("myuniverse.com")
end

it "supports updating the universe_domain" do
make_auth_stubs access_token: "1/abcde"
@client.fetch_access_token!
@client.universe_domain = "anotheruniverse.com"
expect(@client.universe_domain).to eq("anotheruniverse.com")
end
end

context "error in universe_domain" do
before :example do
@universe_domain = Errno::EHOSTDOWN
end

it "results in an error" do
make_auth_stubs access_token: "1/abcde"
expect { @client.fetch_access_token! }
.to raise_error Signet::AuthorizationError
end
end

context "metadata is unavailable" do
describe "#fetch_access_token" do
Expand Down
Loading