0

I have this rather simple python and bash script that should create SA, deploy cloud function (with HTTP trigger) and set scheduler to trigger this function.

main.py

import functions_framework

# Register an HTTP function with the Functions Framework
@functions_framework.http
def my_http_function(request):
  # Your code here

  # Return an HTTP response
  return 'OK'

requirements.txt

functions-framework==3.*

commands.sh

#!/bin/bash

SA_NAME=cf-sa-test
PROJECT=<PROJECT-ID>
REGION=europe-west1
SA_EMAIL=$SA_NAME@$PROJECT.iam.gserviceaccount.com
PROJECT_NUMBER=$(gcloud projects list --filter="$PROJECT" --format="value(PROJECT_NUMBER)")
CF_NAME=my_http_function

gcloud iam service-accounts create $SA_NAME --project=$PROJECT --description="CF SA TEST" --display-name="CF SA TEST"

gcloud functions deploy $CF_NAME --project=$PROJECT --gen2 --region=$REGION --runtime=python311 --source=. --entry-point=my_http_function --trigger-http --run-service-account=$SA_EMAIL --no-allow-unauthenticated

gcloud scheduler jobs create http cf-test-job \
--location=$REGION \
--schedule="0 */6 * * *" \
--uri="$(gcloud functions describe $CF_NAME --gen2 --project=$PROJECT --region=$REGION --format="value(serviceConfig.uri)")" \
--http-method=GET \
--oidc-service-account-email=$SA_EMAIL

When executed commands.sh adds SA, deploy cloud function but it throws error on gcloud scheduler jobs create:

$ ./commands.sh
Created service account [cf-sa-test].
Preparing function...done.                                                                                                                                                                                                                                 
✓ Deploying function...                                                                                                                                                                                                                                    
  ✓ [Build] Logs are available at [https://console.cloud.google.com/cloud-build/builds;region=europe-west1/b219ea91-2cbe-4bfe-a813-27779ecab550?project=<PROJECT-NUMBER>]                                                                                      
  ✓ [Service]                                                                                                                                                                                                                                              
  . [ArtifactRegistry]                                                                                                                                                                                                                                     
  . [Healthcheck]                                                                                                                                                                                                                                          
  . [Triggercheck]                                                                                                                                                                                                                                         
Done.                                                                                                                                                                                                                                                      
You can view your function in the Cloud Console here: https://console.cloud.google.com/functions/details/europe-west1/my_http_function?project=<PROJECT-ID>

buildConfig:
  build: projects/<PROJECT-NUMBER>/locations/europe-west1/builds/b219ea91-2cbe-4bfe-a813-27779ecab550
  entryPoint: my_http_function
  runtime: python311
  source:
    storageSource:
      bucket: gcf-v2-sources-<PROJECT-NUMBER>-europe-west1
      object: my_http_function/function-source.zip
  sourceProvenance:
    resolvedStorageSource:
      bucket: gcf-v2-sources-<PROJECT-NUMBER>-europe-west1
      generation: '1685431646792344'
      object: my_http_function/function-source.zip
environment: GEN_2
labels:
  deployment-tool: cli-gcloud
name: projects/<PROJECT-ID>/locations/europe-west1/functions/my_http_function
serviceConfig:
  allTrafficOnLatestRevision: true
  availableCpu: '0.1666'
  availableMemory: 256M
  ingressSettings: ALLOW_ALL
  maxInstanceCount: 100
  maxInstanceRequestConcurrency: 1
  revision: my-http-function-00001-bij
  service: projects/<PROJECT-ID>/locations/europe-west1/services/my-http-function
  serviceAccountEmail: cf-sa-test@<PROJECT-ID>.iam.gserviceaccount.com
  timeoutSeconds: 60
  uri: https://<FUNCTION-URL>
state: ACTIVE
updateTime: '2023-05-30T07:28:19.023163428Z'
ERROR: (gcloud.scheduler.jobs.create.http) PERMISSION_DENIED: The principal (user or service account) lacks IAM permission "iam.serviceAccounts.actAs" for the resource "cf-sa-test@<PROJECT-ID>.iam.gserviceaccount.com" (or the resource may not exist)

I figured out that maybe scheduler SA needs to have access to cloud function SA so I tried to run

gcloud iam service-accounts add-iam-policy-binding $SA_EMAIL --project=$PROJECT --member=serviceAccount:service-$PROJECT_NUMBER@gcp-sa-cloudscheduler.iam.gserviceaccount.com --role=roles/iam.serviceAccountUser
gcloud iam service-accounts add-iam-policy-binding $SA_EMAIL --project=$PROJECT --member=serviceAccount:service-$PROJECT_NUMBER@gcp-sa-cloudscheduler.iam.gserviceaccount.com --role=roles/iam.serviceAccountTokenCreator

Result was:

Updated IAM policy for serviceAccount [cf-sa-test@<PROJECT-ID>.iam.gserviceaccount.com].
bindings:
- members:
  - serviceAccount:service-<PROJECT-NUMBER>@gcp-sa-cloudscheduler.iam.gserviceaccount.com
  role: roles/iam.serviceAccountTokenCreator
- members:
  - serviceAccount:service-<PROJECT-NUMBER>@gcp-sa-cloudscheduler.iam.gserviceaccount.com
  role: roles/iam.serviceAccountUser
etag: BwX85KXvXas=
version: 1

I waited like 10 minutes just in case but result was the same:

ERROR: (gcloud.scheduler.jobs.create.http) PERMISSION_DENIED: The principal (user or service account) lacks IAM permission "iam.serviceAccounts.actAs" for the resource "cf-sa-test@<PROJECT-ID>.iam.gserviceaccount.com" (or the resource may not exist)

I must be doing some silly mistake because all those steps look correct. What am I doing wrong here? Thanks.

piotrekkr
  • 2,785
  • 2
  • 21
  • 35
  • Just to be sure since it isn't printed anywhere, are you sure that your custom SA exists with that exact email address? – somethingsomething May 30 '23 at 08:36
  • @somethingsomething As you can see in results of `gcloud iam service-accounts add-iam-policy-binding $SA_EMAIL` this account exist: `Updated IAM policy for serviceAccount [cf-sa-test@c.iam.gserviceaccount.com].`. Would be weird if I could update IAM on SA that does not exist :) – piotrekkr May 30 '23 at 08:42

2 Answers2

1

Below 2 scenarios will help you to resolve your issue:

1. Permission issue:

It seems necessary roles were not granted to invoke that specific Cloud Function. Add either the Cloud Function Owner SA or create a new Service Account and give the correct permissions (Cloud Function Invoker role).

Refer to official GCP doc on Set up the service account for details.

To deploy a resource with a user-managed service account, the deployer must have the iam.serviceAccounts.actAs permission for that service account.

Check if you have the permission to the created service account, add if you don’t find it. You need to grant a SA used on Cloud function to have the "iam.serviceAccounts.actAs" permission for the account [doc] on your project, to impersonate it and create the job.

Note: If you're still facing the same issue, double check that the Cloud Scheduler job creation is done on the correct project, or provide me with the commands/API requests that you are making on your pipeline to create that job.

2. Check organization constraint policy :

By default “iam.disableCrossProjectServiceAccountUsage” is enabled.This constraint controls whether you can attach a service account to a resource in another project. Disable the organization policy “iam.disableCrossProjectServiceAccountUsage” in the project and try to deploy the Cloud Functions from the cross project.

See official GCP documentations on Service account impersonation & Creating and editing policies for more details.

Please note that changes to organization policies & permissions can take up to 15 minutes to be fully enforced.

EDIT :

The workaround for now:

  1. Check the "Allow unauthenticated connection" when creating the Cloud Function.

  2. Create the Cloud Scheduler job only with the URL and the "GET" method. No additional authentication is required.

  3. For reference check the Voy post for more details.

Veera Nagireddy
  • 1,656
  • 1
  • 3
  • 12
  • Thanks for this general answer but, I don't have permission problem with creating cloud function. Problem is with creating Cloud Scheduler job that is using service account for OIDC auth. I am project owner and have all permissions on project. I am creator of all SA involved. Service accounts are in same GCP project as Cloud function and Cloud scheduler. I've also added `roles/iam.serviceAccountUser` to `service-@gcp-sa-cloudscheduler.iam.gserviceaccount.com` so it should be able to use service account. But I still get this error... – piotrekkr May 30 '23 at 21:36
0

Thanks to @Veera Nagireddy suggestions to check permissions and a night of good sleep I finally managed to find out why I could not create scheduler job with OIDC. Scheduler did not have permissions to service account because I tried to create scheduler job in different project than cloud function... I was missing --project=$PROJECT in gcloud command and it was using default project instead.

piotrekkr
  • 2,785
  • 2
  • 21
  • 35
  • Glad that my suggestion in the answer (**Note** section) helped you to resolve your issue. Also check **EDIT** in the answer for more information. – Veera Nagireddy May 31 '23 at 08:22