{"id":13646397,"url":"https://github.com/kelseyhightower/grafeas-tutorial","last_synced_at":"2025-08-03T03:07:14.093Z","repository":{"id":65979014,"uuid":"106975370","full_name":"kelseyhightower/grafeas-tutorial","owner":"kelseyhightower","description":"A step by step guide for getting started with Grafeas and Kubernetes.","archived":false,"fork":false,"pushed_at":"2018-12-14T14:01:12.000Z","size":14855,"stargazers_count":189,"open_issues_count":5,"forks_count":44,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-04-30T01:37:42.430Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kelseyhightower.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-10-15T01:36:08.000Z","updated_at":"2025-03-17T19:51:32.000Z","dependencies_parsed_at":"2023-02-19T18:45:38.441Z","dependency_job_id":null,"html_url":"https://github.com/kelseyhightower/grafeas-tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kelseyhightower/grafeas-tutorial","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelseyhightower%2Fgrafeas-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelseyhightower%2Fgrafeas-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelseyhightower%2Fgrafeas-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelseyhightower%2Fgrafeas-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kelseyhightower","download_url":"https://codeload.github.com/kelseyhightower/grafeas-tutorial/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelseyhightower%2Fgrafeas-tutorial/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265414747,"owners_count":23761061,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T01:02:54.606Z","updated_at":"2025-07-15T06:43:48.126Z","avatar_url":"https://github.com/kelseyhightower.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# Grafeas Tutorial\n\nThis tutorial will guide you through testing Grafeas.  In it, you will create a Kubernetes cluster configured to only allow container images signed by a specific key, configurable via a configmap.  Container image signatures will be stored in Grafeas.  To make sure only signed images are allowed, you will start an admission plugin service which finds signatures in Grafeas and verifies them.\n\nCheck out the [Introducing Grafeas](https://cloudplatform.googleblog.com/2017/10/introducing-grafeas-open-source-api-.html) blog post for additional context.\n\n## Tutorial\n\n### Prerequisites\n\nClone this repository:\n\n```\ngit clone https://github.com/kelseyhightower/grafeas-tutorial.git\n```\n\n```\ncd grafeas-tutorial\n```\n\nThe remainder of this tutorial assumes you are in the `grafeas-tutorial` directory.\n\n### Infrastructure\n\nA Kubernetes 1.9+ cluster is required with support for the [ValidatingAdmissionWebhook](https://kubernetes.io/docs/admin/admission-controllers/#validatingadmissionwebhook-alpha-in-18-beta-in-19) alpha feature enabled.\n\nIf you have access to [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine) use the gcloud command to create a 1.9.1 Kubernetes cluster:\n\n```\ngcloud alpha container clusters create grafeas \\\n  --enable-kubernetes-alpha \\\n  --cluster-version 1.9.1-gke.0\n```\n\n\u003e Any Kubernetes 1.9 cluster with support for validating admission webhooks will work. \n\n### Deploy the Grafeas Server\n\n[Grafeas](http://grafeas.io/about) is an open artifact metadata API to audit and govern your software supply chain. In this tutorial Grafeas will be used to store container image signatures. \n\nCreate the Grafeas server deployment:\n\n```\nkubectl apply -f kubernetes/grafeas.yaml\n```\n\n\u003e While in early alpha the Grafeas server leverages an in-memory data store. If the Grafeas server is ever restarted all image signature must be repopulated.\n\n### Generating GPG Signing Keys\n\nIn this section you will generate a [gpg keypair](https://www.gnupg.org/gph/en/manual.html#INTRO) suitable for signing container image metadata.\n\nInstall gpg for you platform:\n\n#### OS X\n\n```\nbrew install gpg2\n```\n\n#### Linux\n\n```\napt-get install gnupg\n```\n\nOnce gpg has been installed generate a signing key:\n\n```\ngpg --quick-generate-key --yes image.signer@example.com \n```\n\nRetrive the ID of the signing key:\n\n```\ngpg --list-keys --keyid-format short\n```\n\n```\n------------------------------------\npub   rsa2048/0CD9D96F 2017-10-17 [SC] [expires: 2019-10-17]\n      510CE141B559A243439EB18926CE52D30CD9D96F\nuid         [ultimate] image.signer@example.com\nsub   rsa2048/2C216B83 2017-10-17 [E]\n```\n\n\u003e Based on the above output the key ID is 0CD9D96F. Your key ID will be different.\n\nStore the ID of your signing key in the `GPG_KEY_ID` env var:\n\n```\nGPG_KEY_ID=\"0CD9D96F\"\n```\n#### Signing Container Image Metadata\n\nContainer images tend to range in size from a few megabytes to multiple gigabytes. Signing and distributing container images can be quite resource intensive so we are going to opt for signing the [image digest](https://cloud.google.com/container-registry/docs/concepts/image-formats#content_addressability) which uniquely identifies a container image.\n\nIn this tutorial the `gcr.io/hightowerlabs/echod` container image will be used for testing. Instead of trusting an image tag such `0.0.1`, which can be reused and point to a different container image later, we are going to trust the image digest. \n\n```\ncat image-digest.txt\n```\n```\nsha256:aba48d60ba4410ec921f9d2e8169236c57660d121f9430dc9758d754eec8f887\n```\n\nSign the image digest text file:\n\n```\ngpg -u image.signer@example.com \\\n  --armor \\\n  --clearsign \\\n  --output=signature.gpg \\\n  image-digest.txt\n```\n\nVerify the signature:\n\n```\ngpg --output - --verify signature.gpg\n```\n\n```\nsha256:aba48d60ba4410ec921f9d2e8169236c57660d121f9430dc9758d754eec8f887\ngpg: Signature made Tue Oct 17 09:11:53 2017 PDT\ngpg:                using RSA key 510CE141B559A243439EB18926CE52D30CD9D96F\ngpg:                issuer \"image.signer@example.com\"\ngpg: Good signature from \"image.signer@example.com\" [ultimate]\n```\n\nIn order for others to verify signed images they must trust and have access to the image signer's public key. Export the image signer's public key:\n\n```\ngpg --armor --export image.signer@example.com \u003e ${GPG_KEY_ID}.pub\n```\n\n### Create a pgpSignedAttestation Occurrence\n\nNow that we have a signed container image, and a public key to verify it, we need to create a [pgpSignedAttestation occurrence](https://github.com/Grafeas/Grafeas/blob/master/samples/server/go-server/api/docs/PgpSignedAttestation.md) using the Grafeas API.\n\nIn a new terminal create a secure tunnel to the grafeas server:\n\n```\nkubectl port-forward \\\n  $(kubectl get pods -l app=grafeas -o jsonpath='{.items[0].metadata.name}') \\\n  8080:8080\n```\n\nCreate the `production` attestationAuthority note:\n\n```\ncurl -X POST \\\n  \"http://127.0.0.1:8080/v1alpha1/projects/image-signing/notes?noteId=production\" \\\n  -d @note.json\n```\n\nGenerate an pgpSignedAttestation occurrence:\n\n```\nGPG_SIGNATURE=$(cat signature.gpg | base64)\n```\n\n```\nRESOURCE_URL=\"https://gcr.io/hightowerlabs/echod@sha256:aba48d60ba4410ec921f9d2e8169236c57660d121f9430dc9758d754eec8f887\"\n```\n\n```\ncat \u003e occurrence.json \u003c\u003cEOF\n{\n  \"resourceUrl\": \"${RESOURCE_URL}\",\n  \"noteName\": \"projects/image-signing/notes/production\",\n  \"attestation\": {\n    \"pgpSignedAttestation\": {\n       \"signature\": \"${GPG_SIGNATURE}\",\n       \"contentType\": \"application/vnd.gcr.image.url.v1\",\n       \"pgpKeyId\": \"${GPG_KEY_ID}\"\n    }\n  }\n}\nEOF\n```\n\nPost the pgpSignedAttestation occurrence:\n\n```\ncurl -X POST \\\n  'http://127.0.0.1:8080/v1alpha1/projects/image-signing/occurrences' \\\n  -d @occurrence.json\n```\n\nAt this point the `gcr.io/hightowerlabs/echod` container image can be verified through the Grafeas API.\n\n\u003e Only the `gcr.io/hightowerlabs/echod` container image identified by the `sha256:aba48d60ba4410ec921f9d2e8169236c57660d121f9430dc9758d754eec8f887` image digest and be verified by the Grafeas API. Additional images require a new occurrence. \n\n### Deploy the Image Signature Webhook\n\nCreate the `image-signature-webhook` configmap and store the image signer's public key: \n\n```\nkubectl create configmap image-signature-webhook \\\n  --from-file ${GPG_KEY_ID}.pub\n```\n\n```\nkubectl get configmap image-signature-webhook -o yaml\n```\n\nCreate the `tls-image-signature-webhook` secret and store the TLS certs:\n\n```\nkubectl create secret tls tls-image-signature-webhook \\\n  --key pki/image-signature-webhook-key.pem \\\n  --cert pki/image-signature-webhook.pem\n```\n\nCreate the `image-signature-webhook` deployment:\n\n```\nkubectl apply -f kubernetes/image-signature-webhook.yaml \n```\n\nCreate the `image-signature-webook` ValidatingWebhookConfiguration:\n\n```\nkubectl apply -f kubernetes/validating-webhook-configuration.yaml\n```\n\n\u003e After you create the validating webhook configuration, the system will take a few seconds to honor the new configuration.\n\n### Testing the Admission Webhook\n\nAttempt to run the `nginx:1.13` container image which does not have an pgpSignedAttestation occurrence in the Grafeas API. Create the `nginx` pod:\n\n```\nkubectl apply -f pods/nginx.yaml\n```\n\nNotice the `nginx` pod was not created and the follow error was returned: \n\n```\nThe  \"\" is invalid: : No matched signatures for container image: nginx:1.13\n```\n\nAttempt to run the `gcr.io/hightowerlabs/echod@sha256:aba48d60ba4410ec921f9d2e8169236c57660d121f9430dc9758d754eec8f887` container image which has an pgpSignedAttestation occurrence in the Grafeas API.\n\n```\nkubectl apply -f pods/echod.yaml \n```\n```\npod \"echod\" created\n```\n\nAt this point the following pods should be running in your cluster:\n\n```\nkubectl get pods\n```\n```\nNAME                                       READY     STATUS    RESTARTS   AGE\nechod                                      1/1       Running   0          5m\ngrafeas-5b5759cbcf-lx8r5                   1/1       Running   0          12m\nimage-signature-webhook-6cc7d6bd74-55blt   1/1       Running   0          8m\n```\n\n\u003e Notice the `nginx` pod was not created because the `nginx:1.13` container image was not verified by the image signature webhook.\n\n## Cleanup\n\nRun the following commands to remove the Kubernetes resources created during this tutorial:\n\n```\nkubectl delete deployments grafeas image-signature-webhook\nkubectl delete pods echod\nkubectl delete svc grafeas image-signature-webhook\nkubectl delete secrets tls-image-signature-webhook\nkubectl delete configmap image-signature-webhook\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkelseyhightower%2Fgrafeas-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkelseyhightower%2Fgrafeas-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkelseyhightower%2Fgrafeas-tutorial/lists"}