I deployed a service on Cloud Run where authentication is needed:
gcloud run deploy my-service --project my-project --image eu.gcr.io/my-project/rest-of-path --platform managed --region europe-west4 --no-allow-unauthenticated
This seems to work fine. However, when I try to access my service from another service (in my case it is Anvil), it gives me a Response [403], which means it refused to authorize it. My Service Account does have the right roles as far as I know: Cloud Run Invoker, Service Account Token Creator, Service Controller. Even if I to add the owner role, it's not working.
This is my code to access my service:
API_URL="https://my-url.run.app/"
def create_signed_jwt(credentials_json, run_service_url):
iat = time.time()
exp = iat + 3600
payload = {
'iss': credentials_json['client_email'],
'sub': credentials_json['client_email'],
'target_audience': run_service_url,
'aud': 'https://www.googleapis.com/oauth2/v4/token',
'iat': iat,
'exp': exp
}
additional_headers = {
'kid': credentials_json['private_key_id']
}
signed_jwt = jwt.encode(
payload,
credentials_json['private_key'],
headers=additional_headers,
algorithm='RS256'
)
return signed_jwt
def exchange_jwt_for_token(signed_jwt):
body = {
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': signed_jwt
}
token_request = requests.post(
url='https://www.googleapis.com/oauth2/v4/token',
headers={
'Content-Type': 'application/x-www-form-urlencoded'
},
data=urllib.parse.urlencode(body)
)
return token_request.json()['id_token']
def get_headers():
"""
Creates the headers for each request to the API on google cloud run
"""
credentials = {
"type": "service_account",
"project_id": "my-project-id",
"private_key_id": my-key-id,
"private_key": "-----BEGIN PRIVATE KEY----- very long token-----END PRIVATE KEY-----\n",
"client_email": "[email protected]",
"client_id": my-client-id,
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": some-standard-url,
"client_x509_cert_url": some-standard-url
}
token = exchange_jwt_for_token(create_signed_jwt(credentials, API_URL))
return {
"Authorization": f"Bearer {token}"
}
def test_request_function():
""" request example url"""
response = requests.get(f'{API_URL}/health', get_headers())
print(test_request_function())
Why is it not possible to authorize?
I encourage you to consider using Google's auth library (for Python) or any other reputable auth library to generate the JWT.
As you're experiencing, crafting JWT's is gnarly and, even when you get it working, you're on the hook for supporting code that would probably be better left to others.
See: Authenticating Service-to-Service
Yields an identity token and hopefully (
200).You can then plug the identity token into e.g. https://jwt.io to inspect it.