{"id":15698845,"url":"https://github.com/rgl/gitlab-ci-validate-jwt","last_synced_at":"2025-05-08T20:58:12.057Z","repository":{"id":57695861,"uuid":"297067340","full_name":"rgl/gitlab-ci-validate-jwt","owner":"rgl","description":"Validate a GitLab CI JWT using the keys available at its jwks endpoint","archived":false,"fork":false,"pushed_at":"2025-03-29T09:00:54.000Z","size":64,"stargazers_count":7,"open_issues_count":0,"forks_count":6,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-08T20:58:04.845Z","etag":null,"topics":["gitlab","gitlab-ci","jwt"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rgl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-09-20T12:05:20.000Z","updated_at":"2025-04-23T11:06:35.000Z","dependencies_parsed_at":"2024-11-09T12:00:42.670Z","dependency_job_id":"3ea0123c-1274-4c74-8e22-0401cef1845b","html_url":"https://github.com/rgl/gitlab-ci-validate-jwt","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgl%2Fgitlab-ci-validate-jwt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgl%2Fgitlab-ci-validate-jwt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgl%2Fgitlab-ci-validate-jwt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgl%2Fgitlab-ci-validate-jwt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rgl","download_url":"https://codeload.github.com/rgl/gitlab-ci-validate-jwt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253149569,"owners_count":21861720,"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":["gitlab","gitlab-ci","jwt"],"created_at":"2024-10-03T19:34:51.795Z","updated_at":"2025-05-08T20:58:12.038Z","avatar_url":"https://github.com/rgl.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"This validates a GitLab CI ID Token JWT using the keys available at its jwks endpoint.\n\nA GitLab CI ID Token JWT is a secret string that can be used to authenticate a particular CI job in 3rd party services (like HashiCorp Vault).\n\nIts available in a CI job as a [custom environment variable defined in the job `id_tokens` property](https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html), as, e.g.:\n\n```yaml\nexample_job:\n  id_tokens:\n    EXAMPLE_ID_TOKEN:\n      aud: https://example.com\n  script:\n    - echo $EXAMPLE_ID_TOKEN\n```\n\nA JWT is a structured string separated by dot characters; for example, a custom ID token JWT, something alike:\n\n```\neyJraWQiOiJjYVJnUUFlSGl1dEgzNXlVcXJMbHpST3RBcGZfYzhaWjZYSHN3RkJ5MERNIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJuYW1lc3BhY2VfaWQiOiIxMCIsIm5hbWVzcGFjZV9wYXRoIjoiZXhhbXBsZSIsInByb2plY3RfaWQiOiIxIiwicHJvamVjdF9wYXRoIjoiZXhhbXBsZS9naXRsYWItY2ktdmFsaWRhdGUtand0IiwidXNlcl9pZCI6IjEiLCJ1c2VyX2xvZ2luIjoicm9vdCIsInVzZXJfZW1haWwiOiJnaXRsYWJfYWRtaW5fMDkzYjgzQGV4YW1wbGUuY29tIiwidXNlcl9hY2Nlc3NfbGV2ZWwiOiJvd25lciIsInBpcGVsaW5lX2lkIjoiMTgiLCJwaXBlbGluZV9zb3VyY2UiOiJwdXNoIiwiam9iX2lkIjoiMzQiLCJyZWYiOiJtYXN0ZXIiLCJyZWZfdHlwZSI6ImJyYW5jaCIsInJlZl9wYXRoIjoicmVmcy9oZWFkcy9tYXN0ZXIiLCJyZWZfcHJvdGVjdGVkIjoidHJ1ZSIsInJ1bm5lcl9pZCI6MiwicnVubmVyX2Vudmlyb25tZW50Ijoic2VsZi1ob3N0ZWQiLCJzaGEiOiI5NWQxOGQ2NmFmZDJjMDYwOWY2YzQxYmQ1MzdhODI3YmViNjk4ZTY0IiwicHJvamVjdF92aXNpYmlsaXR5IjoicHVibGljIiwiY2lfY29uZmlnX3JlZl91cmkiOiJnaXRsYWIuZXhhbXBsZS5jb20vZXhhbXBsZS9naXRsYWItY2ktdmFsaWRhdGUtand0Ly8uZ2l0bGFiLWNpLnltbEByZWZzL2hlYWRzL21hc3RlciIsImNpX2NvbmZpZ19zaGEiOiI5NWQxOGQ2NmFmZDJjMDYwOWY2YzQxYmQ1MzdhODI3YmViNjk4ZTY0IiwianRpIjoiNGJkODc2N2UtM2Q2Ni00OTU4LThiODMtNzA5N2RhZWJjMWE3IiwiaWF0IjoxNzQwODIxMTQyLCJuYmYiOjE3NDA4MjExMzcsImV4cCI6MTc0MDgyNDc0MiwiaXNzIjoiaHR0cHM6Ly9naXRsYWIuZXhhbXBsZS5jb20iLCJzdWIiOiJwcm9qZWN0X3BhdGg6ZXhhbXBsZS9naXRsYWItY2ktdmFsaWRhdGUtand0OnJlZl90eXBlOmJyYW5jaDpyZWY6bWFzdGVyIiwiYXVkIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSJ9.C4lwXJEppq_h0JBgJZGOWyuf19RQL4nE8Jckv90Exy92XTk53y0IXhepYtKgrCTwWmLByzlikfbz6nmfpxDeggBIyAbn6H1NDzg7SqFCMXTx8caQkDyyX4uizKEnB9_z0V1hxwJdhYAiyVB9itHyXQWylwWUsihYORFSgjhK0JsrKp2VTKd304dH4XZ4jc3MSrrUK0251GV6IF0CIUZCSZlkLd1Z6gND4EY7PjSlBpYh9wd9IA9_bXt7_0F34Gvb1P_Ne9y3PFfWRmBOm-9qnHSMxn-rdYeb0LTUUsFc3w4ooO3q61u6XrW-xBgafoeahSqmlLjxPqQPwhM9NKEdiA\n```\n\nWhen split by dot and decoded it has a header, payload and signature.\n\nIn this case, the header is:\n\n```json\n{\n  \"kid\": \"caRgQAeHiutH35yUqrLlzROtApf_c8ZZ6XHswFBy0DM\",\n  \"typ\": \"JWT\",\n  \"alg\": \"RS256\"\n}\n```\n\nThe payload is:\n\n```json\n{\n  \"namespace_id\": \"10\",\n  \"namespace_path\": \"example\",\n  \"project_id\": \"1\",\n  \"project_path\": \"example/gitlab-ci-validate-jwt\",\n  \"user_id\": \"1\",\n  \"user_login\": \"root\",\n  \"user_email\": \"gitlab_admin_093b83@example.com\",\n  \"user_access_level\": \"owner\",\n  \"pipeline_id\": \"18\",\n  \"pipeline_source\": \"push\",\n  \"job_id\": \"34\",\n  \"ref\": \"master\",\n  \"ref_type\": \"branch\",\n  \"ref_path\": \"refs/heads/master\",\n  \"ref_protected\": \"true\",\n  \"runner_id\": 2,\n  \"runner_environment\": \"self-hosted\",\n  \"sha\": \"95d18d66afd2c0609f6c41bd537a827beb698e64\",\n  \"project_visibility\": \"public\",\n  \"ci_config_ref_uri\": \"gitlab.example.com/example/gitlab-ci-validate-jwt//.gitlab-ci.yml@refs/heads/master\",\n  \"ci_config_sha\": \"95d18d66afd2c0609f6c41bd537a827beb698e64\",\n  \"jti\": \"4bd8767e-3d66-4958-8b83-7097daebc1a7\",\n  \"iat\": 1740821142,\n  \"nbf\": 1740821137,\n  \"exp\": 1740824742,\n  \"iss\": \"https://gitlab.example.com\",\n  \"sub\": \"project_path:example/gitlab-ci-validate-jwt:ref_type:branch:ref:master\",\n  \"aud\": \"https://example.com\"\n}\n```\n\nAnd the signature is the value from the 3rd part of the JWT string.\n\nBefore a JWT can be used it must be validated. In this particular example the JWT can be validated with:\n\n```go\nRSASHA256(\n    base64UrlEncode(header) + \".\" + base64UrlEncode(payload),\n    gitLabJwtKeySet.getPublicKey(header.kid))\n```\n\nThe above public key should be retrieved from the GitLab jwks endpoint (e.g. https://gitlab.example.com/oauth/discovery/keys).\n\nTo see how all of this can be done read the [main.go](main.go) file.\n\n## Reference\n\n* https://docs.gitlab.com/ce/ci/examples/authenticating-with-hashicorp-vault/\n* https://gitlab.com/gitlab-org/gitlab-foss/blob/v17.9.1/app/models/ci/build.rb\n* https://gitlab.com/gitlab-org/gitlab-foss/blob/v17.9.1/lib/gitlab/ci/jwt.rb\n* https://gitlab.com/gitlab-org/gitlab-foss/blob/v17.9.1/lib/gitlab/ci/jwt_v2.rb\n* https://gitlab.com/gitlab-org/gitlab-foss/blob/v17.9.1/app/controllers/jwt_controller.rb\n* https://gitlab.com/gitlab-org/gitlab-foss/blob/v17.9.1/app/controllers/jwks_controller.rb\n* JWKS (JSON Web Key Set) endpoint (e.g. https://gitlab.example.com/oauth/discovery/keys) at https://gitlab.com/gitlab-org/gitlab-foss/blob/v17.9.1/config/routes.rb#L47\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frgl%2Fgitlab-ci-validate-jwt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frgl%2Fgitlab-ci-validate-jwt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frgl%2Fgitlab-ci-validate-jwt/lists"}