Skip to content

Commit

Permalink
feat: Allow scopes to be self-signed into jwts
Browse files Browse the repository at this point in the history
  • Loading branch information
bshaffer committed Jul 27, 2021
1 parent 1119fbe commit e67ce40
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 22 deletions.
28 changes: 16 additions & 12 deletions lib/googleauth/service_account.rb
Expand Up @@ -129,7 +129,7 @@ def apply_self_signed_jwt! a_hash
quota_project_id: @quota_project_id
}
key_io = StringIO.new MultiJson.dump(cred_json)
alt = ServiceAccountJwtHeaderCredentials.make_creds json_key_io: key_io
alt = ServiceAccountJwtHeaderCredentials.make_creds json_key_io: key_io, scope: scope
alt.apply! a_hash
end
end
Expand All @@ -154,15 +154,13 @@ class ServiceAccountJwtHeaderCredentials
attr_reader :project_id
attr_reader :quota_project_id

# make_creds proxies the construction of a credentials instance
# Create a ServiceAccountJwtHeaderCredentials.
#
# make_creds is used by the methods in CredentialsLoader.
#
# By default, it calls #new with 2 args, the second one being an
# optional scope. Here's the constructor only has one param, so
# we modify make_creds to reflect this.
def self.make_creds *args
new json_key_io: args[0][:json_key_io]
# @param json_key_io [IO] an IO from which the JSON key can be read
# @param scope [string|array|nil] the scope(s) to access
def self.make_creds options = {}
json_key_io, scope = options.values_at :json_key_io, :scope
new json_key_io: json_key_io, scope: scope
end

# Initializes a ServiceAccountJwtHeaderCredentials.
Expand All @@ -181,6 +179,7 @@ def initialize options = {}
end
@project_id ||= CredentialsLoader.load_gcloud_project_id
@signing_key = OpenSSL::PKey::RSA.new @private_key
@scope = options[:scope]
end

# Construct a jwt token if the JWT_AUD_URI key is present in the input
Expand All @@ -189,7 +188,7 @@ def initialize options = {}
# The jwt token is used as the value of a 'Bearer '.
def apply! a_hash, opts = {}
jwt_aud_uri = a_hash.delete JWT_AUD_URI_KEY
return a_hash if jwt_aud_uri.nil?
return a_hash if jwt_aud_uri.nil? && @scope.nil?
jwt_token = new_jwt_token jwt_aud_uri, opts
a_hash[AUTH_METADATA_KEY] = "Bearer #{jwt_token}"
a_hash
Expand All @@ -211,16 +210,21 @@ def updater_proc
protected

# Creates a jwt uri token.
def new_jwt_token jwt_aud_uri, options = {}
def new_jwt_token jwt_aud_uri = nil, options = {}
now = Time.new
skew = options[:skew] || 60
assertion = {
"iss" => @issuer,
"sub" => @issuer,
"aud" => jwt_aud_uri,
"exp" => (now + EXPIRY).to_i,
"iat" => (now - skew).to_i
}

jwt_aud_uri = nil if @scope

assertion["scope"] = Array(@scope).join " " if @scope
assertion["aud"] = jwt_aud_uri if jwt_aud_uri

JWT.encode assertion, @signing_key, SIGNING_ALGORITHM
end
end
Expand Down
34 changes: 24 additions & 10 deletions spec/googleauth/service_account_spec.rb
Expand Up @@ -44,9 +44,10 @@

include Google::Auth::CredentialsLoader

shared_examples "jwt header auth" do
shared_examples "jwt header auth" do |aud="https://www.googleapis.com/myservice"|
context "when jwt_aud_uri is present" do
let(:test_uri) { "https://www.googleapis.com/myservice" }
let(:test_uri) { aud }
let(:test_scope) { "scope/1 scope/2" }
let(:auth_prefix) { "Bearer " }
let(:auth_key) { ServiceAccountJwtHeaderCredentials::AUTH_METADATA_KEY }
let(:jwt_uri_key) { ServiceAccountJwtHeaderCredentials::JWT_AUD_URI_KEY }
Expand All @@ -56,14 +57,16 @@ def expect_is_encoded_jwt hdr
expect(hdr.start_with?(auth_prefix)).to be true
authorization = hdr[auth_prefix.length..-1]
payload, = JWT.decode authorization, @key.public_key, true, algorithm: "RS256"
expect(payload["aud"]).to eq(test_uri)

expect(payload["aud"]).to eq(test_uri) if not test_uri.nil?
expect(payload["scope"]).to eq(test_scope) if test_uri.nil?
expect(payload["iss"]).to eq(client_email)
end

describe "#apply!" do
it "should update the target hash with a jwt token" do
md = { foo: "bar" }
md[jwt_uri_key] = test_uri
md[jwt_uri_key] = test_uri if test_uri
@client.apply! md
auth_header = md[auth_key]
expect_is_encoded_jwt auth_header
Expand All @@ -74,31 +77,31 @@ def expect_is_encoded_jwt hdr
describe "updater_proc" do
it "should provide a proc that updates a hash with a jwt token" do
md = { foo: "bar" }
md[jwt_uri_key] = test_uri
md[jwt_uri_key] = test_uri if test_uri
the_proc = @client.updater_proc
got = the_proc.call md
auth_header = got[auth_key]
expect_is_encoded_jwt auth_header
expect(got[jwt_uri_key]).to be_nil
expect(md[jwt_uri_key]).to_not be_nil
expect(md[jwt_uri_key]).to_not be_nil if test_uri
end
end

describe "#apply" do
it "should not update the original hash with a jwt token" do
md = { foo: "bar" }
md[jwt_uri_key] = test_uri
md[jwt_uri_key] = test_uri if test_uri
the_proc = @client.updater_proc
got = the_proc.call md
auth_header = md[auth_key]
expect(auth_header).to be_nil
expect(got[jwt_uri_key]).to be_nil
expect(md[jwt_uri_key]).to_not be_nil
expect(md[jwt_uri_key]).to_not be_nil if test_uri
end

it "should add a jwt token to the returned hash" do
md = { foo: "bar" }
md[jwt_uri_key] = test_uri
md[jwt_uri_key] = test_uri if test_uri
got = @client.apply md
auth_header = got[auth_key]
expect_is_encoded_jwt auth_header
Expand All @@ -107,6 +110,7 @@ def expect_is_encoded_jwt hdr
end
end


describe Google::Auth::ServiceAccountCredentials do
ServiceAccountCredentials = Google::Auth::ServiceAccountCredentials
let(:client_email) { "app@developer.gserviceaccount.com" }
Expand Down Expand Up @@ -169,14 +173,24 @@ def cred_json_text
it_behaves_like "jwt header auth"
end

context "when enable_self_signed_jwt is set" do
context "when enable_self_signed_jwt is set with aud" do
before :example do
@client.scope = nil
@client.instance_variable_set(:@enable_self_signed_jwt, true)
end

it_behaves_like "jwt header auth"
end

context "when enable_self_signed_jwt is set with scope" do
before :example do
@client.scope = ['scope/1', 'scope/2']
@client.instance_variable_set(:@enable_self_signed_jwt, true)
end

it_behaves_like "jwt header auth", nil
end

describe "#from_env" do
before :example do
@var_name = ENV_VAR
Expand Down

0 comments on commit e67ce40

Please sign in to comment.