0

The question is very similar to Google Cloud Tasks cannot authenticate to Cloud Run, but I am not using my own domain. I am using cloud run domain itself.

I have followed both below tutorial (both are kind of similar):

https://cloud.google.com/run/docs/triggering/using-tasks

https://cloud.google.com/tasks/docs/creating-http-target-tasks

I am not sure if there is more to the tutorial that I am missing, but below is the complete scenario.

I have some Django REST based APIs running on Google Cloud Run, and they are public. Furthermore, I can use them without any issue (though then need authentication).

I have created a Google Tasks Queue, and I am sending tasks to it using the following code

class QueueTaskMixin:
    def send_task(self, payload=None):
        url = 'https://my-public-cloud-run-app.a.run.app/api/conversation_types/'

        client = tasks_v2.CloudTasksClient(credentials=credentials)
        # `credentials` above belongs to a service account which have all sort of accesses
        # Cloud SQL Admin
        # Cloud SQL Client
        # Cloud SQL Editor
        # Cloud Tasks Admin
        # Cloud Tasks Task Runner
        # Service Account Token Creator
        # Service Account User
        # Cloud Run Admin
        # Cloud Run Invoker
        # Cloud Run Service Agent
        # Storage Admin
  
        parent = client.queue_path(
            project=settings.GS_PROJECT_ID,
            location=settings.GS_Q_REGION,
            queue=settings.GS_Q_NAME)

        task = {
            'http_request':
                {
                    'headers': {
                        'Content-type': 'application/json',
                    },
                    'http_method': tasks_v2.HttpMethod.POST,
                    'url': url,
                    'oidc_token': {
                        'service_account_email': settings.GS_Q_IAM_EMAIL,
                         # GS_Q_IAM_EMAIL is a another Service account that has
                         # Cloud Tasks Enqueuer
                         # Service Account User
                         # Cloud Run Invoker
                    }
                }
        }

        if payload:
            if isinstance(payload, dict):
                payload = json.dumps(payload)
            converted_payload = payload.encode()
            task['http_request']['body'] = converted_payload

        response = client.create_task(
            request={'parent': parent, 'task': task}
        )

        print('Created task {}'.format(response.name))

Now I am getting PERMISSION_DENIED(7): HTTP status code 403 error. My API logs shows the same:

"POST /api/conversation_types/ HTTP/1.1" 403 58 "-" "Google-Cloud-Tasks"
Forbidden: /api/conversation_types/

Now what I am not sure is whether this 403 error is thrown by

  1. Two Google services trying to authorize each other
  2. Or my API. Because my API requires authentication/authorization. As in, a user needs to log in using their username and password, and they will get a JWT token, and then they can call this API.

Referring to the documentation, I am not sure where I have to provide my APIs username/password/JWT token. The documentation says:

To authenticate between Cloud Tasks and an HTTP Target handler, Cloud Tasks creates a header token. This token is based on the credentials in the Cloud Tasks Enqueuer service account, identified by its email address.

Do I need to add this service account email address into my APIs as a user? Do I use oidc or oauth?

Any comments or answers much appreciated.

Update 1 - After removing auth from my API, Cloud Tasks is able to call the API successfully. So now how do I auth Cloud Task to be able to run my API?

Update 2 - Tried using OAuthToken and got error

400 Invalid url for HttpRequest.oauth_token. The hostname in the url must end with ".googleapis.com"

Looks like will have to go for OIDC token only.

Update 3 - Google Docs says:

OIDC tokens are signed JSON Web Tokens (JWT) and are used primarily to assert identity and not to provide any implicit authorization against a resource, unlike OAuth tokens, which do provide access.

Does that mean OIDC tokens cannot be used for authorization? Because I am getting authorization error here. Can't use OIDC, can't use OAuth, what to use then?

Update 4 As per comments from @johnhanley, I have updated my API to accept Bearer token for authentication. But even if my API is getting the token correctly, it won't be able to authorize it and will give invalid token error (I verified it using curl command. In both cases where token format is incorrect or token is incorrect, API simply return 403 Forbidden error).

Anyone can tell how to give the password (I can generate that in my API for the service account user email) to the service account so that using that password and email ID as username, OIDC can generate a token and use it to authenticate. Or am I going in the wrong direction?

Niyojan
  • 544
  • 1
  • 6
  • 23
  • In front of Cloud Run is the Identity Aware Proxy (IAP). IAP manages authorization for Cloud Run. IAP uses an OIDC Identity Token in the HTTP **Authorization** header. The scheme is **Bearer**. If your application is also implementing authorization, your application must use a different header for its authorization. Note: IAP can also use the Proxy-Authorization header but Google services such as Cloud Tasks do not set that header for authenticated requests. – John Hanley Aug 22 '21 at 12:25
  • I have updated my API to use `Bearer` token. Still same error. @JohnHanley – Niyojan Aug 23 '21 at 10:00
  • How does OIDC know the username/password to generate the token that it will use to send in `Bearer`. Considering it is using the email ID of service account as username, from where it is taking the password? I have actually created a user in my API with same email as service account, but I dont know where to give the password. – Niyojan Aug 23 '21 at 10:33
  • 1
    The process of **authentication** might use usernames/passwords or other types of secrets. Google implements **Identity-Based Authorization** for IAP. Identity Tokens do not have a password. They do have a signature that verifies the contents. Study the Google docs on authentication and authorization. – John Hanley Aug 23 '21 at 17:42

2 Answers2

0

Check the presence of Cloud Tasks service agent (service-[project-number]@gcp-sa-cloudtasks.iam.gserviceaccount.com) in your IAM Admin Console Page and grant him the roles/iam.serviceAccountTokenCreator role.

MBHA Phoenix
  • 2,029
  • 2
  • 11
  • 25
  • Not required, its there by default. Documentation says `The Cloud Tasks service account with this role granted is automatically created when you enable the Cloud Tasks API, unless you enabled it prior to March 19, 2019, in which case you must add the role manually.` I enabled the API last week. – Niyojan Aug 23 '21 at 10:36
  • I also enabled the API recently on my project but I couldn’t see it on my IAM page. And did you check for the specific role I mentioned : Service Account Token Creator ? – MBHA Phoenix Aug 23 '21 at 10:50
  • Sorry, I understood your answer incorrectly, I have already created the Service agent manually, I did mention in my questions, its `GS_Q_IAM_EMAIL` and it does have `Service Account Token Creator` role. – Niyojan Aug 23 '21 at 11:03
0

Don't turn off authentication, it does work. Here are some steps to follow in the form of bash shell commands and comments to explain the steps

Set some variables

PROJECT_ID='my-gcp-project-123'
TASK_ENQUER_SVC_ACCOUNT='tasks-creator-svc'
TASKS_SRV_ACCOUNT='cloud-tasks-svc'
CLOUD_RUN_SRV_ACCOUNT='cloud-run-svc'
GPC_REGION='australia-southeast1'

Create a cloud task queue

gcloud tasks queues create my-task-queue

Create a service account for sending the tasks - you need cloudtasks.enqueuer and serviceAccountUser

gcloud iam service-accounts create $TASK_ENQUER_SVC_ACCOUNT \
    --display-name "Service account for sending cloud tasks"
gcloud projects add-iam-policy-binding ${PROJECT_ID} --member \
    serviceAccount:$TASK_ENQUER_SVC_ACCOUNT@${PROJECT_ID}.iam.gserviceaccount.com \
    --role "roles/logging.logWriter"
# cloudtasks.enqueuer role is needed to create tasks
gcloud projects add-iam-policy-binding ${PROJECT_ID} --member \
    serviceAccount:$TASK_ENQUER_SVC_ACCOUNT@${PROJECT_ID}.iam.gserviceaccount.com \
    --role "roles/cloudtasks.enqueuer"
# service account user role is needed for permission iam.serviceAccounts.actAs
# so a task can be generated as service account $TASKS_SRV_ACCOUNT
gcloud projects add-iam-policy-binding ${PROJECT_ID} --member \
    serviceAccount:$TASK_ENQUER_SVC_ACCOUNT@${PROJECT_ID}.iam.gserviceaccount.com \
    --role "roles/iam.serviceAccountUser"

Create the service account for running the Cloud Run service

gcloud iam service-accounts create $CLOUD_RUN_SRV_ACCOUNT \
    --display-name "Service account for running cloud run service"
gcloud projects add-iam-policy-binding ${PROJECT_ID} --member \
    serviceAccount:$CLOUD_RUN_SRV_ACCOUNT@${PROJECT_ID}.iam.gserviceaccount.com \
    --role "roles/logging.logWriter"

Create the service account that cloud-tasks will use - no special permissions needed

gcloud iam service-accounts create $TASKS_SRV_ACCOUNT \
    --display-name "Service account for the cloud-tasks"
gcloud projects add-iam-policy-binding ${PROJECT_ID} --member \
    serviceAccount:$TASKS_SRV_ACCOUNT@${PROJECT_ID}.iam.gserviceaccount.com \
    --role "roles/logging.logWriter"

Deploy your cloud run service with ingress=all and no-allow-unauthenticated
Cloud tasks does not use serverless VPC connectors, so ingress needs to be all

gcloud run deploy my-cloud-run-service \
    --image=us.gcr.io/${PROJECT_ID}/my-docker-image \
    --concurrency=1 \
    --cpu=2 \
    --memory=250Mi \
    --max-instances=10 \
    --platform=managed \
    --region=$GPC_REGION \
    --no-allow-unauthenticated \
    --port=8080 \
    --service-account=$CLOUD_RUN_SRV_ACCOUNT@${PROJECT_ID}.iam.gserviceaccount.com \
    --ingress=all \
    --project=${PROJECT_ID}

You must now give permission for TASKS_SRV_ACCOUNT to call the Cloud Run target service

gcloud run services add-iam-policy-binding my-cloud-run-service \
    --member=serviceAccount:$TASKS_SRV_ACCOUNT@${PROJECT_ID}.iam.gserviceaccount.com \
    --role=roles/run.invoker \
    --region=$GPC_REGION

You can then send your cloud tasks with authentication tokens as per instructions at this step https://cloud.google.com/tasks/docs/creating-http-target-tasks#token
The service_account_email parameter is the TASKS_SRV_ACCOUNT created above

rossco
  • 593
  • 12
  • 22
  • Still doesn't work. API needs auth, you can't call it without JWT token, where to give that token? – Niyojan Sep 11 '21 at 09:32
  • I use this technique and it works, I do not store or supply the JWT token anywhere. The Cloud Tasks service will authenticate itself against Cloud Run as long as permissions have been granted correctly – rossco Sep 12 '21 at 04:54
  • Can you add the error message you recieved after following the steps above? – rossco Sep 12 '21 at 04:54