{"id":45277731,"url":"https://github.com/glueops/gatekeeper.platform.glueops.dev","last_synced_at":"2026-02-21T02:00:38.488Z","repository":{"id":339664947,"uuid":"1161808537","full_name":"GlueOps/gatekeeper.platform.glueops.dev","owner":"GlueOps","description":"Managed by github-org-manager","archived":false,"fork":false,"pushed_at":"2026-02-21T00:03:13.000Z","size":16456,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-21T01:49:01.550Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/GlueOps.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-19T14:42:58.000Z","updated_at":"2026-02-20T20:40:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/GlueOps/gatekeeper.platform.glueops.dev","commit_stats":null,"previous_names":["glueops/gatekeeper.platform.glueops.dev"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/GlueOps/gatekeeper.platform.glueops.dev","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GlueOps%2Fgatekeeper.platform.glueops.dev","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GlueOps%2Fgatekeeper.platform.glueops.dev/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GlueOps%2Fgatekeeper.platform.glueops.dev/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GlueOps%2Fgatekeeper.platform.glueops.dev/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GlueOps","download_url":"https://codeload.github.com/GlueOps/gatekeeper.platform.glueops.dev/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GlueOps%2Fgatekeeper.platform.glueops.dev/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29671513,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T00:11:43.526Z","status":"online","status_checked_at":"2026-02-21T02:00:07.432Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":"2026-02-21T02:00:27.397Z","updated_at":"2026-02-21T02:00:38.482Z","avatar_url":"https://github.com/GlueOps.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GlueOps Core Gatekeeper\n\u003cimg width=\"960\" height=\"1088\" alt=\"Gemini_Generated_Image_4z69mb4z69mb4z69\" src=\"https://github.com/user-attachments/assets/2685397d-4055-4678-9601-908b4c827cf4\" /\u003e\n\nGlueOps Core Gatekeeper is a small Go HTTP service that acts as a **deployment dependency gate** for Kubernetes.\n\nIt lets applications define their own dependency checks (via a `Gate` custom resource), and provides a simple API that returns:\n\n- **200 OK** when all checks pass (dependencies are ready)\n- **409 Conflict** when any check is not ready yet (safe to retry)\n- **4xx** for invalid specs or authorization issues\n\nThis is especially useful when you need ordering logic across a mix of:\n- Argo CD-managed apps (sync waves / hooks)\n- Helm-managed components (hooks, Jobs, controllers)\n- “Out-of-band” resources not represented as Argo Applications\n\nA common pattern is to use an **Argo CD PreSync hook Job** to block deployment of an app until the Gatekeeper reports that prerequisites (DB, migrations, external dependencies) are ready.\n\n---\n\n## Key features\n\n- **Per-app dependency definition**: each app owns a `Gate` object in its namespace.\n- **Safe-by-default multi-tenancy**:\n  - Requests authenticate via Kubernetes **ServiceAccount tokens**.\n  - Access is authorized using Kubernetes **SubjectAccessReview** (delegated authorization).\n- **Namespace policy modes** controlled by a namespace label:\n  - `customer` (default): gate lookup and checks are namespace-local\n  - `platform`: optionally allows cross-namespace checks and/or gate lookups (restricted by allowlists)\n- **Simple HTTP API** designed for hook-based retries.\n\n---\n\n## How it works\n\n1. A caller (usually an Argo CD hook Job) calls Gatekeeper with its ServiceAccount token:\n   - `GET /check?gate=\u003cgate-name\u003e`\n2. Gatekeeper performs a `TokenReview` to authenticate the token.\n3. Gatekeeper determines the caller namespace from the ServiceAccount identity.\n4. Gatekeeper loads the `Gate` CR and evaluates each check.\n5. Each check is authorized with a `SubjectAccessReview` for the caller.\n6. Gatekeeper responds with JSON and appropriate HTTP status.\n\n---\n\n## Installation\n\n### 1) Install the CRD\n\nApply the `Gate` CRD:\n\n```bash\nkubectl apply -f crd.yaml\n```\n\n### 2) Create Gatekeeper RBAC\n\nApply Gatekeeper RBAC (TokenReview + SubjectAccessReview + read-only allowlisted resources + Gate status patch):\n\n```bash\nkubectl apply -f rbac.yml\n```\n\n### 3) Deploy the Gatekeeper service\n\nYour deployment should run with the Gatekeeper ServiceAccount and expose port 8080.\n(Provide your own deployment YAML, Helm chart, or Kustomize overlay.)\n\n\n## Configuration\n\nGatekeeper supports configuration via environment variables.\n\n### Gate CRD location\n| Env var                    | Default                        | Description               |\n| -------------------------- | ------------------------------ | ------------------------- |\n| `GATEKEEPER_GATE_GROUP`    | `platform.onglueopshosted.com` | API group of the Gate CRD |\n| `GATEKEEPER_GATE_VERSION`  | `v1alpha1`                     | API version               |\n| `GATEKEEPER_GATE_RESOURCE` | `gates`                        | plural resource name      |\n\n\n### Platform mode can be restricted to specific namespaces.\n\n| Env var                                          | Default                | Description                                                                           |\n| ------------------------------------------------ | ---------------------- | ------------------------------------------------------------------------------------- |\n| `GATEKEEPER_PLATFORM_ALLOWED_NAMESPACES`         | `glueops-core,nonprod` | CSV list of namespaces allowed for platform cross-namespace access                    |\n| `GATEKEEPER_PLATFORM_ALLOWED_NAMESPACE_PREFIXES` | `glueops-core-`        | CSV list of namespace prefixes allowed (e.g. `glueops-core-` allows `glueops-core-*`) |\n\n\n### HTTP\n\n| Env var | Default | Description      |\n| ------- | ------- | ---------------- |\n| `PORT`  | `8080`  | HTTP listen port |\n\n## Namespace policy modes\n\nGatekeeper chooses the mode based on a label on the caller’s namespace:\n\nLabel key:\n```yaml\ngatekeeper.platform.onglueopshosted.com/mode: customer|platform\n```\n\n### customer mode (default)\n\n- Gate lookup must be in the caller namespace.\n- Checks must be in the same namespace as the Gate.\n\nThis is intended for customer/self-service namespaces.\n\n### platform mode\n\n- Cross-namespace Gate lookup is allowed via ?ns=... only if the namespace is allowed by:\n  - GATEKEEPER_PLATFORM_ALLOWED_NAMESPACES OR\n  - GATEKEEPER_PLATFORM_ALLOWED_NAMESPACE_PREFIXES\n- Cross-namespace checks are allowed using the same allow rules.\n\nThis is intended for platform-controlled automation and core namespaces.\n\n## The Gate Custom Resource\n\nA Gate defines a list of checks. Each check must set exactly one check type.\n\nExample:\n```yaml\napiVersion: platform.onglueopshosted.com/v1alpha1\nkind: Gate\nmetadata:\n  name: keycloak-prod\n  namespace: nonprod\nspec:\n  strict: true\n  checks:\n    - id: postgres\n      deploymentAvailable:\n        name: keycloak-pg-database-prod\n        minAvailableReplicas: 1\n```\n\n### Supported check types\n\n`deploymentAvailable`\n\nChecks a Deployment is Available and has at least N available replicas.\n```yaml\n- id: api\n  deploymentAvailable:\n    name: my-api\n    minAvailableReplicas: 2\n```\n\n`statefulSetReady`\n\nChecks a StatefulSet has at least N ready replicas (and optionally is fully updated).\n```yaml\n- id: postgres\n  statefulSetReady:\n    name: postgres\n    minReadyReplicas: 1\n    requireUpdatedRevision: true\n```\n\n`jobComplete`\n\nChecks a Job has completed successfully.\n```yaml\n- id: migrate\n  jobComplete:\n    name: my-app-migrate\n```\n\n`serviceReadyEndpoints`\nChecks a Service has at least N ready endpoint addresses (via EndpointSlices).\n```yaml\n- id: redis\n  serviceReadyEndpoints:\n    name: redis\n    minReadyAddresses: 1\n```\n\n`podLabelReady`\nChecks at least N Pods matching a label selector are Ready.\n```yaml\n- id: workers\n  podLabelReady:\n    selector: \"app=my-worker\"\n    minReadyPods: 2\n```\n\n\u003e Note: If you allow very broad selectors, this may list many pods. Prefer selectors that are specific to the app.\n\n## HTTP API\n### GET /healthz\n\nSimple health endpoint.\n```bash\ncurl -i http://gatekeeper:8080/healthz\n```\n\n### GET /check?gate=\u003cname\u003e\n\nEvaluates the Gate in the caller namespace (customer mode), or by default in the caller namespace (platform mode too).\n- 200 OK → all checks passed\n- 409 Conflict → at least one check is blocking (safe to retry)\n\nExample:\n```bash\nTOKEN=\"$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\"\ncurl -sS -H \"Authorization: Bearer $TOKEN\" \\\n  \"http://gatekeeper:8080/check?gate=keycloak-prod\"\n```\n\n### GET /check?gate=\u003cname\u003e\u0026ns=\u003cnamespace\u003e (platform mode)\n\nIn platform mode only, allows evaluating a Gate in another namespace if allowed by allowlists.\n```bash\nTOKEN=\"$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\"\ncurl -sS -H \"Authorization: Bearer $TOKEN\" \\\n  \"http://gatekeeper:8080/check?gate=keycloak-prod\u0026ns=nonprod\"\n```\n\n### GET /explain?gate=\u003cname\u003e[\u0026ns=\u003cnamespace\u003e]\n\nSame evaluation logic as /check, but always returns JSON with 200 OK (useful for debugging).\n```\ncurl -sS -H \"Authorization: Bearer $TOKEN\" \\\n  \"http://gatekeeper:8080/explain?gate=keycloak-prod\u0026ns=nonprod\" | jq .\n```\n\n### JSON response format\n```json\n{\n  \"gate\": \"keycloak-prod\",\n  \"namespace\": \"nonprod\",\n  \"ready\": false,\n  \"mode\": \"platform\",\n  \"results\": [\n    {\n      \"id\": \"postgres\",\n      \"ready\": false,\n      \"message\": \"availableReplicas 0 \u003c 1\"\n    }\n  ]\n}\n```\n\n## Argo CD usage (recommended)\n\nThe most common integration is an Argo CD PreSync hook Job that blocks until Gatekeeper returns 200.\n\nExample: PreSync hook Job\n```yaml\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: gate-wait\n  annotations:\n    argocd.argoproj.io/hook: PreSync\n    argocd.argoproj.io/hook-delete-policy: HookSucceeded\nspec:\n  backoffLimit: 0\n  template:\n    spec:\n      serviceAccountName: gate-waiter\n      restartPolicy: Never\n      containers:\n        - name: wait\n          image: curlimages/curl:8.5.0\n          command: [\"/bin/sh\", \"-c\"]\n          args:\n            - |\n              set -e\n              TOKEN=\"$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\"\n              until curl -sf -H \"Authorization: Bearer ${TOKEN}\" \\\n                \"http://glueops-core-gatekeeper.glueops-core-gatekeeper.svc.cluster.local:8080/check?gate=keycloak-prod\"; do\n                echo \"dependencies not ready yet\"; sleep 5\n              done\n              echo \"dependencies ready\"\n```\nThis pattern makes Argo CD wait until prerequisites are ready before continuing the sync.\n\n## RBAC / Authorization model\n\nGatekeeper uses delegated authorization:\n- The caller’s ServiceAccount must have RBAC permissions to read the resources referenced in the Gate checks.\n- Gatekeeper verifies permissions with SubjectAccessReview before reading.\n\nExample: minimal namespace Role for a hook SA\n\nIn many cases, you can give the PreSync hook SA a minimal Role in its namespace:\n```yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: gate-waiter-read\n  namespace: nonprod\nrules:\n  - apiGroups: [\"apps\"]\n    resources: [\"deployments\",\"statefulsets\"]\n    verbs: [\"get\"]\n  - apiGroups: [\"batch\"]\n    resources: [\"jobs\"]\n    verbs: [\"get\"]\n  - apiGroups: [\"\"]\n    resources: [\"services\"]\n    verbs: [\"get\"]\n  - apiGroups: [\"\"]\n    resources: [\"pods\"]\n    verbs: [\"list\"]\n  - apiGroups: [\"discovery.k8s.io\"]\n    resources: [\"endpointslices\"]\n    verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: gate-waiter-read\n  namespace: nonprod\nsubjects:\n  - kind: ServiceAccount\n    name: gate-waiter\n    namespace: nonprod\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: gate-waiter-read\n```\n\n### Local development\nRun locally against a cluster\nupdate / create .env file\n```text\nGATEKEEPER_PLATFORM_ALLOWED_NAMESPACE_PREFIXES=\"glueops-core-\"\nGATEKEEPER_PLATFORM_ALLOWED_NAMESPACES=\"glueops-core,nonprod\"\nKUBECONFIG=/home/vscode/.kube/config\n```\n```bash\ngo run .\n```\n\n### Test with a ServiceAccount token\n\nFor example, to test platform mode from a platform namespace:\n```bash\nNS=glueops-core-gatekeeper\nSA=glueops-core-gatekeeper\nTOKEN=$(kubectl -n \"$NS\" create token \"$SA\" --duration=10m)\n\ncurl -sS -H \"Authorization: Bearer $TOKEN\" \\\n  \"http://localhost:8080/check?gate=keycloak-prod\u0026ns=nonprod\" | jq .\n```\n\nIf you get a 403:\n- verify the caller SA has RBAC to read the referenced resources (or intentionally doesn’t)\n- verify the target namespace is allowed by GATEKEEPER_PLATFORM_ALLOWED_NAMESPACES/..._PREFIXES\n\n### Troubleshooting\njq: parse error\n\nYou are likely receiving a plain-text error response. Run with -i to see the status code:\n```bash\ncurl -i -H \"Authorization: Bearer $TOKEN\" \\\n  \"http://localhost:8080/check?gate=keycloak-prod\u0026ns=nonprod\"\n```\n#### 403 forbidden with SubjectAccessReview\n\nThe caller ServiceAccount does not have permission to read a resource referenced by a check.\n\nFix by granting minimal RBAC in the namespace for that SA (see RBAC example above).\n\n#### 404 gate not found\n\nThe Gate name or namespace is wrong, or you are calling without \u0026ns= in platform mode.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglueops%2Fgatekeeper.platform.glueops.dev","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fglueops%2Fgatekeeper.platform.glueops.dev","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglueops%2Fgatekeeper.platform.glueops.dev/lists"}