CONTEXT:
For a project I am working on I have setup service to service authentication using a service account for a gRPC API sat behind the Extensible Service Proxy (ESP) as per the instructions here. For reference my authentication configuration looks like the following
authentication:
providers:
- id: google_service_account
issuer: <service-account-email>
jwks_uri: https://www.googleapis.com/robot/v1/metadata/x509/<service-account-email>
rules:
- selector: "*"
requirements:
- provider_id: google_service_account
I then have a ruby client that reads in a service account key (attained through the GCP console) from disk and generates a JWT using the googleauth
gem to be used to authenticate with the API.
module Authenticated
def credentials
@credentials ||= Google::Auth::ServiceAccountJwtHeaderCredentials
.make_creds(json_key_io: service_account_json_io)
.apply(jwt_aud_uri: ENV.fetch('SERVICE_NAME'))
end
def self.service_account
@service_account ||= StringIO.new(
File.read('/etc/secrets/service-account.json'),
)
end
private
def service_account_json_io
Authenticated.service_account.tap(&:rewind)
end
end
Currently this is in a working state and the client is able to authentication with ESP.
THE PROBLEM:
Since implementing the above I have another client application needing to reuse the same API. This means having to generate a new service account key and mounting that into the new application for authentication. Eventually if I create more clients then having to securely store many service account keys is error prone and a potential security risk. Instead I'd like to use the GCE metadata server to generate the JWT from lets say the default compute engine service account (although I may use a different account later) and pass this through to the ESP.
What I have tried so far is to change the ESP authentication configuration as follows
authentication:
providers:
- id: google_service_account
issuer: https://accounts.google.com
jwks_uri: https://www.googleapis.com/robot/v1/metadata/x509/<gce-default-service-account-email>
rules:
- selector: "*"
requirements:
- provider_id: google_service_account
and updated the ruby client to request a JWT from the metadata server as follows
module Authenticated
METADATA_SERVER_IDENTITY_URI = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?format=full&audience='.freeze
def credentials
{ authorization: "Bearer #{identity_jwt}" }
end
private
def identity_jwt
http = Net::HTTP.new(identity_uri.hostname)
http.request(identity_request).body
end
def identity_request
Net::HTTP::Get.new(identity_uri).tap do |req|
req.add_field('Metadata-Flavor', 'Google')
end
end
def identity_uri
URI.parse("#{METADATA_SERVER_IDENTITY_URI}https://#{ENV['SERVICE_NAME']}")
end
end
This again generates a JWT however this time with the issuer set to https://accounts.google.com
(as reflected in the ESP authentication configuration). However this time the client is unable to authenticate with the ESP reporting Error: KEY_RETRIEVAL_ERROR
THE QUESTION:
Is it possible to authenticate against the ESP using a JWT generated through the GCE metadata server? And what are the configuration steps?
By removing the jwks
key from the Google Cloud Endpoints configuration yaml it forces ESP to use OpenID discovery to fetch the jwks_uri
that will be used to obtain the correct JWK
. This can be seen in the api manager configuration.
Given the same issuer defined in the question (accounts.google.com
) the code will generate a URL to https://accounts.google.com/.well-known/openid-configuration containing the jwks_uri
. ESP uses the jwks
specified here to verify a JWT.