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": "my-credential-name@my-project-id.iam.gserviceaccount.com",
"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
import urllib
import google.auth.transport.requests
import google.oauth2.id_token
import os
endpoint=os.getenv("ENDPOINT")
audience=os.getenv("AUDIENCE")
req = urllib.request.Request(endpoint)
auth_req = google.auth.transport.requests.Request()
id_token = google.oauth2.id_token.fetch_id_token(auth_req, audience)
bearer = f"Bearer {id_token}"
print(bearer)
req.add_header("Authorization", bearer)
response = urllib.request.urlopen(req)
print(response.code)
NOTE You can also use Application Default Credentials with the Google library making the code shorter.
Q=72491606
python3 -m venv venv
source venv/bin/activate
python3 -m pip install google-auth
python3 -m pip install requests
BILLING=...
PROJECT=stackoverflow-${Q}
REGION=...
ACCOUNT="tester"
EMAIL="${ACCOUNT}@${PROJECT}.iam.gserviceaccount.com"
gcloud projects create ${PROJECT}
gcloud beta billing projects link ${PROJECT} \
--billing-account=${BILLING}
gcloud services enable run.googleapis.com \
--project=${PROJECT}
gcloud iam service-accounts create ${ACCOUNT} \
--project=${PROJECT}
gcloud iam service-accounts keys create ${PWD}/${ACCOUNT}.json \
--iam-account=${EMAIL} \
--project=${PROJECT}
gcloud projects add-iam-policy-binding ${PROJECT} \
--member=serviceAccount:${ACCOUNT}@${PROJECT}.iam.gserviceaccount.com \
--role=roles/run.invoker
export GOOGLE_APPLICATION_CREDENTIALS=${PWD}/${ACCOUNT}.json
# Deploy Cloud Run example
gcloud run deploy hello \
--image="gcr.io/cloudrun/hello" \
--no-allow-unauthenticated \
--region=${REGION} \
--platform=managed \
--project=${PROJECT}
# Get ENDPOINT==AUDIENCE
export ENDPOINT=$(\
gcloud run services describe hello \
--project=${PROJECT} \
--region=${REGION} \
--format="value(status.url)")
export AUDIENCE=${ENDPOINT}
python3 main.py
Yields an identity token and hopefully (200
).
You can then plug the identity token into e.g. https://jwt.io to inspect it.