Skip to content
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
20 changes: 20 additions & 0 deletions auth/service-to-service/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

source "https://rubygems.org"

gem "functions_framework"
gem "googleauth", "~> 1.17"
gem "rspec", "~> 3.0"
gem "webrick"
105 changes: 105 additions & 0 deletions auth/service-to-service/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.9.0)
public_suffix (>= 2.0.2, < 8.0)
base64 (0.3.0)
cloud_events (0.9.0)
diff-lcs (1.6.2)
faraday (2.14.3)
faraday-net_http (>= 2.0, < 3.5)
json
logger
faraday-net_http (3.4.4)
net-http (~> 0.5)
functions_framework (1.7.0)
cloud_events (>= 0.7.0, < 2.a)
puma (>= 4.3.0, < 9.a)
rack (>= 2.1, < 4.a)
google-cloud-env (2.4.0)
base64 (~> 0.2)
faraday (>= 1.0, < 3.a)
google-logging-utils (0.2.0)
googleauth (1.17.3)
faraday (>= 1.0, < 3.a)
google-cloud-env (~> 2.2)
google-logging-utils (~> 0.1)
jwt (>= 1.4, < 4.0)
os (>= 0.9, < 2.0)
pstore (~> 0.1)
signet (>= 0.16, < 2.a)
json (2.21.2)
jwt (3.2.0)
base64
logger (1.7.0)
net-http (0.9.1)
uri (>= 0.11.1)
nio4r (2.7.5)
os (1.1.4)
pstore (0.2.1)
public_suffix (7.0.5)
puma (8.0.2)
nio4r (~> 2.0)
rack (3.2.7)
rspec (3.13.2)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.6)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.8)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.7)
signet (0.22.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 4.0)
uri (1.1.1)
webrick (1.9.2)

PLATFORMS
ruby
x86_64-linux

DEPENDENCIES
functions_framework
googleauth (~> 1.17)
rspec (~> 3.0)
webrick

CHECKSUMS
addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
cloud_events (0.9.0) sha256=f0a8421ff064a6c96f05c2e974d1f5957a6c1455a256a2f0e93926ff0c5c4797
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
faraday (2.14.3) sha256=1882247e6766615c8220b4392bf1d27f6ebb63d8e28267587cef1fb0bf37f278
faraday-net_http (3.4.4) sha256=0e78af151747ed1b00f33e25973b4bc220d7f16c00c39676817c8b12331eb588
functions_framework (1.7.0) sha256=e92e9f02209eb5e8d50c1ee65c1c965b152a82a8a08a9697dd1ac5f1d019bbac
google-cloud-env (2.4.0) sha256=01cb35ecee5d1c4d1cd4deaed8bb5bddc838e296bcc2a35c5d1ddd07ce40d28d
google-logging-utils (0.2.0) sha256=675462b4ea5affa825a3442694ca2d75d0069455a1d0956127207498fca3df7b
googleauth (1.17.3) sha256=9a655455885c0c17a8b70537acd2a992a7c7842c43c06c6612dfb0d93e755d58
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
jwt (3.2.0) sha256=5419b1fe37b1da0982bd07051f573a8b8789ab724c2aa7e785e4784a3ed217d7
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
nio4r (2.7.5) sha256=6c90168e48fb5f8e768419c93abb94ba2b892a1d0602cb06eef16d8b7df1dca1
os (1.1.4) sha256=57816d6a334e7bd6aed048f4b0308226c5fb027433b67d90a9ab435f35108d3f
pstore (0.2.1) sha256=03904d0f2c66579e96d1e6704cdabc0c88df7ea8ed8782d9f3569f6f6c702c1a
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
puma (8.0.2) sha256=c8ed871dfbbe66448ea9ffd46692342d9804d4071522b52b5331b7b6e7b686fb
rack (3.2.7) sha256=93e13e1c24f93556671d85d2d79fa228c3485815c50d7e2f265b5330c6528fb7
rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
signet (0.22.0) sha256=b76d495ccb07ad35dbc89f3e920665a9d8ed717141955034005d7843dcfe4780
uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
webrick (1.9.2) sha256=beb4a15fc474defed24a3bda4ffd88a490d517c9e4e6118c3edce59e45864131

BUNDLED WITH
2.3.15
34 changes: 34 additions & 0 deletions auth/service-to-service/app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This application acts as both the target function and relay function
# for testing service-to-service authentication using Cloud Functions.

require_relative "service_auth"

FunctionsFramework.http "make_get_request_cloud-echo" do |_request|
"Success!"
end

FunctionsFramework.http "make_get_request_cloud" do |_request|
target_url = ENV["TARGET_URL"]

if target_url.nil? || target_url.empty?
raise "TARGET_URL environment variable is missing"
end

writer = StringIO.new
make_get_request writer, target_url, target_url
writer.string
end
44 changes: 44 additions & 0 deletions auth/service-to-service/service_auth.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# [START cloudrun_service_to_service_auth]
require "net/http"
require "uri"
require "googleauth"

def make_get_request writer, target_url, audience
credentials = Google::Auth.get_application_default target_audience: audience
token_info = credentials.fetch_access_token!
id_token = token_info["id_token"] if token_info
raise "ID token is missing from credentials. Ensure you are using a service account or credentials that support ID tokens." if id_token.nil?

uri = URI(target_url)
request = Net::HTTP::Get.new uri

request["Authorization"] = "Bearer #{id_token}"

response = Net::HTTP.start uri.hostname, uri.port, use_ssl: uri.scheme == "https" do |http|
http.open_timeout = 10
http.read_timeout = 10
http.request request
end

response.value

writer.write response.body
rescue StandardError => e
raise "Failed to make get request: #{e.message}"
end

# [END cloudrun_service_to_service_auth]
116 changes: 116 additions & 0 deletions auth/service-to-service/spec/service_auth_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Copyright 2026 Google, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require_relative "../service_auth"
require "rspec"
require "googleauth"
require "open3"

describe "Service to service auth sample" do
before :all do
@project_id = ENV["GOOGLE_CLOUD_PROJECT"]
@credentials = ENV["GOOGLE_APPLICATION_CREDENTIALS"]
@env_vars = @credentials ? { "CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE" => @credentials } : {}

@location = ENV["LOCATION_ID"] || "us-central1"
@entry_point = "make_get_request_cloud"
@target_name = "#{@entry_point}-echo"
@runtime_version = "ruby32"

# Initial Setup. Deploy target function
deploy_cmd = [
"gcloud", "functions", "deploy", @target_name,
"--entry-point=#{@target_name}",
"--runtime=#{@runtime_version}",
"--no-allow-unauthenticated",
"--project=#{@project_id}",
"--trigger-http",
"--source=."
]
puts "Running:#{deploy_cmd.join ' '}"
stdout, stderr, status = Open3.capture3(@env_vars, *deploy_cmd)

# If the Cloud Functions API is disabled in the CI test project, skip the rest of the group
unless status.success?
if stderr.include?("PERMISSION_DENIED") && stderr.include?("Cloud Functions API")
skip "Cloud Functions API not enabled in this project (#{@project_id}). Skipping tests."
else
raise "Setup: Deploy target function failed:\n#{stderr}"
end
end

# Deploy relay function
@target_url = "https://#{@location}-#{@project_id}.cloudfunctions.net/#{@target_name}"

deploy_relay_cmd = [
"gcloud", "functions", "deploy", @entry_point,
"--entry-point=#{@entry_point}",
"--runtime=#{@runtime_version}",
"--no-allow-unauthenticated",
"--project=#{@project_id}",
"--trigger-http",
"--set-env-vars=TARGET_URL=#{@target_url}",
"--source=."
]

puts "Running: #{deploy_relay_cmd.join ' '}"
stdout, stderr, status = Open3.capture3(@env_vars, *deploy_relay_cmd)
raise "Setup: Deploy relay function failed:\n#{stderr}" unless status.success?

@base_url = "https://#{@location}-#{@project_id}.cloudfunctions.net/"
end

after :all do
next if @base_url.nil? # We skipped setup

# Teardown the target function
delete_target_cmd = ["gcloud", "functions", "delete", @target_name, "--project=#{@project_id}", "--quiet"]
puts "Running: #{delete_target_cmd.join ' '}"
_, stderr, status = Open3.capture3(@env_vars, *delete_target_cmd)
puts "Teardown: Delete function #{@target_name} failed: #{stderr}" unless status.success?

# Teardown the relay function
delete_relay_cmd = ["gcloud", "functions", "delete", @entry_point, "--project=#{@project_id}", "--quiet"]
puts "Running: #{delete_relay_cmd.join ' '}"
_, stderr, status = Open3.capture3(@env_vars, *delete_relay_cmd)
puts "Teardown: Delete function #{@entry_point} failed: #{stderr}" unless status.success?
end

it "makes an authenticated get request to the target url" do
url = "#{@base_url}#{@entry_point}"

max_attempts = 5
attempts = 0
success = false

while attempts < max_attempts && !success
attempts += 1
buffer = StringIO.new

begin
make_get_request buffer, url, url

expect(buffer.string).to eq("Success!")
success = true
rescue StandardError, RSpec::Expectations::ExpectationNotMetError => e
if attempts >= max_attempts
raise "Failed after #{max_attempts} attempts. Last error: #{e.message}"
else
puts "make_get_request attempt #{attempts} failed: #{e.message}. Retrying in 10s..."
sleep 10
end
end
end
end
end
5 changes: 2 additions & 3 deletions eventarc/generic/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@

puts "\nHEADERS:"
headers = request.env.select { |k, _v| k.start_with? "HTTP_" }
.collect { |key, val| [key.sub(/^HTTP_/, ""), val] }
.collect { |key, val| "#{key}: #{val}" }
.sort
headers = headers.collect { |key, val| [key.sub(/^HTTP_/, ""), val] }
headers = headers.collect { |key, val| "#{key}: #{val}" }.sort
headers.each do |key, value|
if key != "Authorization"
puts "#{key}: #{value}<br>"
Expand Down
Loading