Cloud Functions deployments are failing with PERMISSION_DENIED errors because the Binary Authorization policy is rejecting the container images being used.

Here’s what’s actually broken: The Binary Authorization system, designed to prevent unauthorized code from running, is acting as a gatekeeper. It’s currently configured to only allow container images that have been signed by a specific, trusted authority. The images being generated for your Cloud Functions deployments don’t meet this signature requirement, leading to the PERMISSION_DENIED rejection.

This is happening because Binary Authorization, when enabled, inspects every container image before it’s allowed to run. It checks for a digital signature from an authorized attestor. If the signature is missing or invalid, it blocks the deployment to ensure only verified code executes.

Here are the common causes and how to fix them:

Cause 1: Binary Authorization is Enabled but No Attestors are Configured for Cloud Functions

Diagnosis: Check your Binary Authorization policy configuration. You can do this via the Google Cloud Console (Security -> Binary Authorization) or using gcloud:

gcloud container binauthz policy describe --project=<YOUR_PROJECT_ID>

Look for a defaultAdmissionRule that requires attestation and check if any attestors are listed. If the policy is enabled but has no attestors specified for the Cloud Functions platform, this is your problem.

Fix: You need to create an attestor and then add it to your Binary Authorization policy for Cloud Functions.

  1. Create an attestor:
    gcloud container binauthz attestors create my-function-attestor \
      --project=<YOUR_PROJECT_ID>
    
  2. Add the attestor to your policy:
    gcloud container binauthz policy update \
      --project=<YOUR_PROJECT_ID> \
      --add-attestor=projects/<YOUR_PROJECT_ID>/attestors/my-function-attestor \
      --require-attestation \
      --resource-type=cloud_functions
    
    This command updates the policy to require attestation for cloud_functions resources and explicitly links your my-function-attestor.

Why it works: This directly addresses the missing link. By creating an attestor and associating it with the policy for Cloud Functions, you’re telling Binary Authorization who is allowed to sign off on your function images.

Cause 2: Images are Not Being Signed by the Configured Attestor

Diagnosis: If you have an attestor configured, the next step is to ensure that the images being deployed are actually being signed. This often involves your CI/CD pipeline. Check your CI/CD logs for steps that generate attestations. For example, if you’re using Cloud Build, look for steps that call gcloud container binauthz attestations sign or a similar command.

Fix: Integrate signing into your deployment pipeline. If using Cloud Build, you can add a step like this after your image is built and pushed to Artifact Registry:

# Example Cloud Build step for signing
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
  entrypoint: 'bash'
  args:
  - '-c'
  - |
    gcloud container binauthz attestations sign \
      --project=<YOUR_PROJECT_ID> \
      --attestor=projects/<YOUR_PROJECT_ID>/attestors/my-function-attestor \
      --artifact-url=$(cat /workspace/image_digest.txt) \
      --key-file=/secrets/cloudbuild/attestor_key.json
  secretEnv:
    'PKCS11_PIN': 'YOUR_PKCS11_PIN'
    'PKCS11_TOKEN_LABEL': 'YOUR_PKCS11_TOKEN_LABEL'
    'PKCS11_MODULE_PATH': 'YOUR_PKCS11_MODULE_PATH'
  volumes:
  - name: 'attestor-key'
    path: '/secrets/cloudbuild'

Note: The secretEnv and volumes are for signing using a Cloud KMS key. Adjust as per your signing method.

Why it works: This ensures that every image pushed for deployment is cryptographically signed by the key associated with your attestor. Binary Authorization then verifies this signature before allowing the function to be deployed.

Cause 3: Incorrect Attestor Key or Permissions for Signing

Diagnosis: If your CI/CD pipeline reports signing errors, or if attestations are being generated but not verified by Binary Authorization, it’s likely a key management issue. Check the service account your CI/CD runner (e.g., Cloud Build service account) is using. Does it have the roles/containeranalysis.attestorAdmin or roles/cloudkms.cryptoKeyEncrypterDecrypter (if using KMS) IAM role on the attestor or KMS key?

Fix: Grant the necessary IAM roles to the service account performing the signing. For example, if Cloud Build is signing:

gcloud projects add-iam-policy-binding <YOUR_PROJECT_ID> \
  --member="serviceAccount:<YOUR_CLOUDBUILD_SERVICE_ACCOUNT_EMAIL>" \
  --role="roles/containeranalysis.attestorAdmin" \
  --condition=None

If using Cloud KMS for signing, ensure the service account has roles/cloudkms.cryptoKeyEncrypterDecrypter for the specific KMS key used by the attestor.

Why it works: The signing process requires permissions to access and use the private key (or KMS key) associated with the attestor. Granting these roles provides the necessary authorization for the signing operation.

Cause 4: Policy is Too Restrictive (e.g., AlwaysDeny or Wrong Resource Type)

Diagnosis: Review your Binary Authorization policy (gcloud container binauthz policy describe). If the defaultAdmissionRule.enforcementMode is set to ALWAYS_DENY and there are no valid attestations, it will block everything. Also, confirm the defaultAdmissionRule.evaluationEvent is set to DEPLOYMENT. If the policy is configured for a different resource type (e.g., GKE clusters only) and not Cloud Functions, it won’t apply.

Fix: Adjust the enforcementMode to ENFORCED_BLOCK_ON_FAILURE if you want deployments to proceed if attestations can be verified but fail to be generated for some reason (though ALWAYS_DENY is the most secure). Crucially, ensure the policy is applied to cloud_functions by specifying --resource-type=cloud_functions when updating the policy.

gcloud container binauthz policy update \
  --project=<YOUR_PROJECT_ID> \
  --require-attestation \
  --attestor=projects/<YOUR_PROJECT_ID>/attestors/my-function-attestor \
  --resource-type=cloud_functions \
  --enforcement-mode=ENFORCED_BLOCK_ON_FAILURE # Or ALWAYS_DENY

Why it works: This ensures the policy is actively looking for attestations specifically for Cloud Functions deployments and defines how it should behave when it finds or doesn’t find them.

Cause 5: Using a Different Container Registry Than Expected

Diagnosis: Binary Authorization policies can be scoped to specific container registries. If your Cloud Functions are deploying images from a registry not covered by your policy (e.g., images are in Artifact Registry but the policy only explicitly trusts Container Registry), they will be rejected. Check the gcr.io or pkg.dev paths in your policy.

Fix: Update your Binary Authorization policy to include the correct registry path. If your functions use images from Artifact Registry (e.g., us-central1-docker.pkg.dev/my-project/my-repo/my-image:tag), you need to configure the policy to recognize this.

gcloud container binauthz policy update \
  --project=<YOUR_PROJECT_ID> \
  --add-attestor=projects/<YOUR_PROJECT_ID>/attestors/my-function-attestor \
  --require-attestation \
  --resource-type=cloud_functions \
  --global-policy-allow-list=us-central1-docker.pkg.dev

Note: The --global-policy-allow-list is used for GKE. For Cloud Functions, the policy applies to all images pushed to Artifact Registry or Container Registry within the project. If you need finer-grained control, you might need to manage attestations per image.

Why it works: This explicitly tells Binary Authorization which registries are considered trustworthy, allowing images from those locations to pass the initial check, provided they meet attestation requirements.

Cause 6: Clock Skew on the Signing Machine

Diagnosis: This is less common but can happen if the system performing the signing has a significantly different system time than Google’s servers. This can cause signature validation to fail because timestamps in the signature are considered invalid. Check system logs on your signing machine for any time synchronization errors.

Fix: Ensure your signing environment (especially if it’s on-premises or a custom VM) has its system clock synchronized with a reliable NTP server. For Cloud Build, this is generally not an issue as Google manages the build environment’s time.

Why it works: Digital signatures often have validity periods (not-before, not-after timestamps). If the signing machine’s clock is drastically off, the generated signature might appear expired or not yet valid to the verification system, even if the cryptography is otherwise sound.

The next error you’ll likely encounter after fixing these is related to the specific attestor’s key management or the signing process itself failing due to insufficient permissions or misconfiguration in your CI/CD pipeline.

Want structured learning?

Take the full Cloud-functions course →