NOTE
This is a walkthrough I wrote in 2019 and migrated from my Notion. Things may have changed a lot since then.
This post is based on the official tutorial: Securing Your GKE Deployments with Binary Authorization
Why
Verify the container image is verified (or comply the whatever policy) before deploying it on a Kubenete cluster
This implies, you must have a GKE cluster first
- Create a cluster: gcloud beta container clusters create --enable-binauthz --zone us-central1-a binauthz-codelab- Assume we already have a project, this can be done in the cloud shell of that project.
- The cluster name will be binauthz-codelab
- not sure about the beta, not sure if it is GA now
- looks like --enable-binauthzis necessary, but not sure what it is doing
- not sure if --zone us-central1-ais required.
 
- Get the created cluster to the context for kubectlby:gcloud container clusters get-credentials binauthz-codelab --zone us-central1-a- Then, kubectlwill always be processing that GKE cluster binauthz-codelab
 
- Then, 
- Assume we have a docker image: FROM alphine; CMD tail -f /dev/null. This image will simply keep waiting forever.- we need to push it the google container registry (GCR), so that GKE can pull it from there.
- To do so, we need to name our image correctly
- like: docker build -t [us.gcr.io/${project_id}/hello-world](http://us.gcr.io/${project_id}/hello-world) ./
- Note the [us.gcr.io/{project_id}/hello-world) does not have a *tag, (e.g: xxx:tag),* GCR will make add a :latest at the end automatically in the registry.
 
- like: 
 
- To do so, we need to name our image correctly
- gcloud auth configure-docker —quiet
- docker push us.gcr.io/${project_id}/hello-world
- Then we should be able see it in the UI
 
- we need to push it the google container registry (GCR), so that GKE can pull it from there.
- Now we need to deploy the docker to our GKE cluster
- kubectl create deployment hello-world --image=us.gcr.io/${project_id}/hello-word
- kubectl get podsto check the pods are running
 
so we can see, here we can deploy whatever an image from our registry
But, when things go wild, we can’t be sure all the images in the registry are verified to be deployed, for example, a dev image to prod cluster.
so we need to verify before use, and now comes the binary authorization
Background
- Many GCP features are implemented, and made available as an API, so we can find them in the API marketplace
- Each enabled API will add a service account (most the case, not thoroughly checked)
- Form: “service-{PROJECT_NUMBER}@gcp-sa-{service name}.iam.gserviceaccount.com”
- Why: Sometime (actually very commonly) the service (the API) want to read data from another service (API) to achieve some functionalities, we have to grant the permission of accessing the data from the second service to the service account created by the first service.
 
 
- Each enabled API will add a service account (most the case, not thoroughly checked)
- Most of the gcloudcommands should be just a wrapper of their corresponding REST requests. So, nearly all thegcloudcommands below should all have acurlreplacement. Also true for the opposite way.
APIs Required for the Codelab proj
- Container Analysis API container.googleapis.com
- For everything about GKE cluster
- And Docker container image registry
 
- Binary Authorization API binaryauthorization.googleapis.com
- Main role here
- This will add a service-{PROJECT_NUMBER}@gcp-sa-binaryauthorization.iam.gserviceaccount.com
 
- KMS API cloudkms.googleapis.com
- To manage (generate, use, refer?) cryptographic signing keys.
 
Enable the APIs:
## enable GKE to create and manage your cluster
gcloud services enable container.googleapis.com
## enable BinAuthz to manage a policy on the cluster
gcloud services enable binaryauthorization.googleapis.com
## enable KMS to manage cryptographic signing keys
gcloud services enable cloudkms.googleapis.comMoving pieces in this game
1. Container Analysis API
This is the guy that actually prevents an arbitrary image being deployed
This is part of the container API
But might be relevant to the --enable-binauthz flag.
It has a ‘note’ service. We can create a note.
cat > ./create_note_request.json << EOM
{
  "attestation": {
    "hint": {
      "human_readable_name": "This note represents an attestation authority"
    }
  }
}
EOM
 
NOTE_ID=my-attestor-note
 
curl -vvv -X POST \
    -H "Content-Type: application/json"  \
    -H "Authorization: Bearer $(gcloud auth print-access-token)"  \
    --data-binary @./create_note_request.json  \
    "https://containeranalysis.googleapis.com/v1/projects/${PROJECT_ID}/notes/?noteId=${NOTE_ID}"
 
## Verify by:
curl -vvv  \
    -H "Authorization: Bearer $(gcloud auth print-access-token)" \
    "https://containeranalysis.googleapis.com/v1/projects/${PROJECT_ID}/notes/${NOTE_ID}"Note that:
- JSON payload itself is the note, although the codelab calls it create_note_request.json
- The NOTE_IDwill be used later. And this is the only identifier of the note.
- The curl to the containeranalysis API. There is only REST api and client API. There is no gcloud container xxxcounterpart of it, not even ingcloud beta container
The note must also be registered with the binary authorization service (detailed later)
2. Binary Authorization
First, we need an attestor
ATTESTOR_ID=my-binauthz-attestor
 
gcloud container binauthz attestors create $ATTESTOR_ID \
    --attestation-authority-note=$NOTE_ID \
    --attestation-authority-note-project=${PROJECT_ID}
 
## verify by:
gcloud container binauthz attestors listSecond we need to make sure the binary authz service account can access data from the container analysis service (grant IAM permission)
PROJECT_NUMBER=$(gcloud projects describe "${PROJECT_ID}"  --format="value(projectNumber)")
BINAUTHZ_SA_EMAIL="service-${PROJECT_NUMBER}@gcp-sa-binaryauthorization.iam.gserviceaccount.com"
 
cat > ./iam_request.json << EOM
{
  'resource': 'projects/${PROJECT_ID}/notes/${NOTE_ID}',
  'policy': {
    'bindings': [
      {
        'role': 'roles/containeranalysis.notes.occurrences.viewer',
        'members': [
          'serviceAccount:${BINAUTHZ_SA_EMAIL}'
        ]
      }
    ]
  }
}
EOM
 
## to grant the necessary IAM role
curl -X POST  \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $(gcloud auth print-access-token)" \
    --data-binary @./iam_request.json \
    "https://containeranalysis.googleapis.com/v1/projects/${PROJECT_ID}/notes/${NOTE_ID}:setIamPolicy"3. KMP, create and keys
We need a asymmetric-signing pair, create is with KMS and link it with our attestor.
KEY_LOCATION=global
KEYRING=binauthz-keys
KEY_NAME=codelab-key
KEY_VERSION=1
 
## Key ring is created first. We will needs a location
gcloud kms keyrings create "${KEYRING}" --location="${KEY_LOCATION}"
 
## Creates a key pair from the key ring and location. And the key
## will be identified as KEY_NAME
gcloud kms keys create "${KEY_NAME}" \
    --keyring="${KEYRING}" --location="${KEY_LOCATION}" \
    --purpose asymmetric-signing  --default-algorithm="ec-sign-p256-sha256"
 
## Link the attestor created by binary authz with the key
## Not sure why we need that many info about the key though.
gcloud beta container binauthz attestors public-keys add  \
    --attestor="${ATTESTOR_ID}"  \
    --keyversion-project="${PROJECT_ID}"  \
    --keyversion-location="${KEY_LOCATION}" \
    --keyversion-keyring="${KEYRING}" \
    --keyversion-key="${KEY_NAME}" \
    --keyversion="${KEY_VERSION}"Note that, we can check the created key in the page: https://pantheon.corp.google.com/security/kms
After linking, we should be able to see we now have +1 public key:
gcloud container binauthz attestors list4. Real game starts by an attestor signing an image
First, always use digest for signing and prod deployment, do not use tag
tag can point to different builds (and it is supposed to do so)
digest is unique for each build
So we now needs to get the digest from a build (which can be identified by tag like :latest)
## container_path should be us.gcr.io/${project_id}/hello-world
DIGEST=$(gcloud container images describe ${CONTAINER_PATH}:latest \
    --format='get(image_summary.digest)')Second, sign
gcloud beta container binauthz attestations sign-and-create  \
    --artifact-url="${CONTAINER_PATH}@${DIGEST}" \
    --attestor="${ATTESTOR_ID}" \
    --attestor-project="${PROJECT_ID}" \
    --keyversion-project="${PROJECT_ID}" \
    --keyversion-location="${KEY_LOCATION}" \
    --keyversion-keyring="${KEYRING}" \
    --keyversion-key="${KEY_NAME}" \
    --keyversion="${KEY_VERSION}"Note:
- The artifact-urlpoints to the image: us.gcr.io/${project_id}/hello-world@{digest}- Note that, this is kind of a full url of the image with digest
 
- The way to identify a build is: us.gcr.io/{digest}
- Needs the Attestor’s ID
- And needs the KEY_RING and KEY_NAME, note that one attestor can have multiple public keys associated, so we need to know which one to use here.
- But we still needs a lot of other information
- keyversion-project
- attestor-project
- So the attestor project can be separated?!
 
- keyversion-location
- This is user defined before
 
- keyversion
- This is user defined before
 
 
 
This makes an attestation
To verify attestation:
gcloud container binauthz attestations list \
   --attestor=$ATTESTOR_ID --attestor-project=${PROJECT_ID}5. Policy should match. If not, update it
We need to make sure we have a policy defined on binary authz service matching our attestation mechanism
cat << EOF > updated_policy.yaml
    globalPolicyEvaluationMode: ENABLE
    defaultAdmissionRule:
      evaluationMode: REQUIRE_ATTESTATION
      enforcementMode: ENFORCED_BLOCK_AND_AUDIT_LOG
      requireAttestationsBy:
      - projects/${PROJECT_ID}/attestors/${ATTESTOR_ID}
EOF
 
gcloud container binauthz policy import updated_policy.yamlNote that:
- The requireAttestationBy:field- It specifies the required attestor
- It is a list, so, new problem: AND OR?
 
- The evaluationModeisREQUIRE_ATTESTATION- Must be a pre-set rule
 
- defaultAdmissionRule- considering the final command: gcloud container binauthz policy- This
 
 
- considering the final command: 
6. Create deployment with attested image
kubectl create deployment hello-world-signed --image="${CONTAINER_PATH}@${DIGEST}"This will get the deployment created.
kubectl get pods
7. Cleanup
Delete cluster gcloud container clusters delete binauthz-codelab --zone us-central1-a
Delete image: gcloud container images delete ${container_path}@${digest} --force-delete-tags
Delete attestor: gcloud container binauthz attestors delete my-binauthz-attestor
Delete container analysis note:
curl -vvv -X DELETE  \
    -H "Authorization: Bearer $(gcloud auth print-access-token)" \
    "https://containeranalysis.googleapis.com/v1/projects/${PROJECT_ID}/notes/${NOTE_ID}"Command list
gcloud (or gcloud beta)
- container
- clusters
- create
- gcloud beta container clusters create --enable-binauthz --zone us-central1-a binauthz-codelab
- To create a GKE cluster
 
- get-credentials
- gcloud container clusters get-credentials binauthz-codelab --zone us-central1-a
- To set the GKE cluster to current context
 
- delete
 
- create
- binauthz
- attestors
- create
- gcloud container binauthz attestors create $ATTESTOR_ID \ --attestation-authority-note=$NOTE_ID \ --attestation-authority-note-project=${PROJECT_ID}
- Identified by ATTESTOR_ID
- Linked with note by NOTE_ID- Also needs the owner project of the note (?)
 
 
- public-keys
- add
- gcloud beta container binauthz attestors public-keys add \ --attestor="${ATTESTOR_ID}" \ --keyversion-project="${PROJECT_ID}" \ --keyversion-location="${KEY_LOCATION}" \ --keyversion-keyring="${KEYRING}" \ --keyversion-key="${KEY_NAME}" \ --keyversion="${KEY_VERSION}"
- To add a key (generated by KMS) to an attestor
 
 
- add
- list
- gcloud container binauthz attestors list
- To list created attestors, also show the number of public keys of each attestors
 
- delete
- gcloud container binauthz attestors delete my-binauthz-attestor
 
 
- create
- attestations
- sign-and-create
- gcloud beta container binauthz attestations sign-and-create \ --artifact-url="${CONTAINER_PATH}@${DIGEST}" \ --attestor="${ATTESTOR_ID}" \ --attestor-project="${PROJECT_ID}" \ --keyversion-project="${PROJECT_ID}" \ --keyversion-location="${KEY_LOCATION}" \ --keyversion-keyring="${KEYRING}" \ --keyversion-key="${KEY_NAME}" \ --keyversion="${KEY_VERSION}"- I guess --attestor-projectis the owner project ofATTESTOR_ID- because ATTESTOR_ID is definitely not universally unique
 
- and --keyversion-projectis the owner project of the KMS key info (key name, key ring, etc)
 
- I guess 
- To sign an image, and creates an attestation
 
- list
- gcloud container binauthz attestations list \ --attestor=$ATTESTOR_ID --attestor-project=${PROJECT_ID}
 
 
- sign-and-create
- policy
- import
- gcloud container binauthz policy import updated_policy.yaml
- To import/update the policy
- Note that, there is only one policy
 
 
 
- import
 
- attestors
- images
- describe
- gcloud container images describe ${CONTAINER_PATH}:latest \ --format='get(image_summary.digest)- This prints out the image digest
 
 
- delete
- gcloud container images delete ${container_path}@${digest} --force-delete-tags
 
 
- describe
 
- clusters
- kms
- keyrings
- create
- gcloud kms keyrings create "${KEYRING}" --location="${KEY_LOCATION}"
- To create a keyring
 
 
- create
- keys
- create
- gcloud kms keys create "${KEY_NAME}" \ --keyring="${KEYRING}" --location="${KEY_LOCATION}" \ --purpose asymmetric-signing --default-algorithm="ec-sign-p256-sha256"
- To create a key from key ring
 
 
- create
 
- keyrings
- auth
- configure-docker
- gcloud auth configure-docker —quiet
- To set some docker context, so that docker push xxxwill work correct
 
 
- configure-docker
- services
- enable
- gcloud services enable container.googleapis.com
- To enable some API services
 
 
- enable
kubectl
- create
- deployment
- kubectl create deployment hello-world --image=us.gcr.io/${project_id}/hello-word
- kubectl create deployment hello-world-signed --image="${CONTAINER_PATH}@${DIGEST}"
- To deploy an image to the current cluster
 
 
- deployment
- get
- pods
- kubectl get pods
- To get the deployed pods in the current cluster
 
 
- pods
curl
- post
- containeranalysis.googleapis.com/v1/projects/{note_id}
- To create a note, there is no gcloudcomamnd counterpart
 
- To create a note, there is no 
 
- containeranalysis.googleapis.com/v1/projects/{note_id}
- get
- containeranalysis.googleapis.com/v1/projects/{note_id}
- To retrieve the created note
 
 
- containeranalysis.googleapis.com/v1/projects/{note_id}
- delete
- containeranalysis.googleapis.com/v1/projects/{note_id}
- To delete the created note
 
 
- containeranalysis.googleapis.com/v1/projects/{note_id}
docker
- push
- docker push us.gcr.io/${project_id}/hello-world
 
- build
- docker build -t [us.gcr.io/${project_id}/hello-world](http://us.gcr.io/${project_id}/hello-world) ./
 
Interconnections
- Note is just a note ( I guess). It does not enable the verification. It probably will just add a comment or log somewhere when creating deployment from an attested image
- When an attestor is linked with a note, it probably will be able to add the note to its log or the image owner project’s log with the content in the note, when an attestation is created with the image. To label an human readable label to say the image is attested.
- Policy is the real enabler of the checking, more specifically, it is the gobalPolicyEvluationMode: ENABLE
- Policy can require attestation from multiple attestors from multiple projects (see the project ID and attestor ID in the yaml file)
- I feel like there are missing pieces, cause fetching the attestor from another project should require some permissions
- Note that the roles/containeranalysis.notes.occurrences.viewershould not be relevant to this (I guess)
 
- Attestors are granted with keys generated from KMS
- But where is the permission, I don’t see anywhere saying the attestor’s owner project or its service account has the permission to get the key from the KMS owner project
- Well, these are Google-managed service accounts, their concrete permissions are hidden, but pretty much all needed to do whatever the API supposed to do.
 
 
- But where is the permission, I don’t see anywhere saying the attestor’s owner project or its service account has the permission to get the key from the KMS owner project
GCP Projects (Not confirmed yet)
Cluster Project (aka. Deployment Project)
This is the project pulling the image (MLLP docker image) from the Container Project
The Cluster must be created with --enable-binauthz option gcloud beta container clusters create --enable-binauthz --zone <zone> <cluster name>
- Or it can be updated with the binauthz support
This project stores the deployment policy.
Needs to confirm:
- Needs to enable containeranalysis and binaryauthorization API?
- Needs to enable container.googleapis.com?
Container Project (Aka. Builder Project)
This is the the project contains the built docker image
To use binary authorization feature, we do NOT need to change anything on this project (in many cases, this project is not managed by the image users)
Attestor Project
Note that, each attestation is actually a container analysis note occurrence. There is actually no real ‘attestation’ type.
Needs container analysis API, because container analysis notes are stored in this project.
Analysis note creation is done by REST request, not gcloud commands.
Needs binary authorization API. Only then we have a service account: service-${ATTESTOR_PROJECT_NUMBER}@gcp-sa-binaryauthorization.iam.gserviceaccount.com and we can bind role: roles/containeranalysis.notes.occurrences.viewer to it.
Usually, the signing keys are also stored in this project (though I think the key can be stored in another Key Project, but in practice, I don’t see much benefit of doing like so…)
The attestors are created and stored in this project, they should have their public key with them registered.
Note Project
Needs container analysis API, analysis note and note occurrences are store in this project
Attestation Project
The project that stores attestations.
KMS Key Project
If using the GCP KMS service to provide the PKIX key, we can use the GCP KMS service
API Explanation
Container Analysis
Store Container Analysis Note and Note Occurrences
- Note Project
Binary Authorization
Create attestor, attestation, apply policy
- Attestor Project
- To create attestor
 
- Attestation Project
- To create attestation
 
- Deployer Project
- To import and use policy, enable binary authorization
 
KMS
Create cryptographic keys
- KMS Project
Extra Permission Settings
Attestor To Access Container Analysis Note
Account: Attestor’s binary authorization service account
Target Resource: projects/{NOTE_ID}
Role: roles/containeranalysis.notes.occurrences.viewer
Deployer To Attestor
Account: Deployer’s binary authorization service account
Target Resource: projects/{ATTESTOR_ID}
Role: roles/binaryauthorization.attestorsVerifier
Set on ${ATTESTOR_PROJECT} with gcloud cmd
Multiple Project Setup
References
Binary Authorization Document:
https://cloud.google.com/binary-authorization/docs/
Official CodeLab of Binary Authorization (Single Project Setup):
https://codelabs.developers.google.com/codelabs/cloud-binauthz-intro/#0
Official Multi-Project Setup Document:
https://cloud.google.com/binary-authorization/docs/multi-project-setup-cli