{"id":15816570,"url":"https://github.com/lbroudoux/open-policy-agent-k8s","last_synced_at":"2025-08-17T01:33:20.441Z","repository":{"id":145929783,"uuid":"328956758","full_name":"lbroudoux/open-policy-agent-k8s","owner":"lbroudoux","description":"Open Policy Agent and Gatekeeper exploration on Kubernetes","archived":false,"fork":false,"pushed_at":"2021-01-18T14:53:21.000Z","size":543,"stargazers_count":1,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-06T05:05:13.354Z","etag":null,"topics":["gatekeeper","kubernetes","open-policy-agent","openshift"],"latest_commit_sha":null,"homepage":"","language":"Open Policy Agent","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lbroudoux.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-01-12T10:53:13.000Z","updated_at":"2021-01-18T14:53:23.000Z","dependencies_parsed_at":null,"dependency_job_id":"538617ec-f520-485c-b9ac-cb148bcfc081","html_url":"https://github.com/lbroudoux/open-policy-agent-k8s","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/lbroudoux/open-policy-agent-k8s","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lbroudoux%2Fopen-policy-agent-k8s","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lbroudoux%2Fopen-policy-agent-k8s/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lbroudoux%2Fopen-policy-agent-k8s/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lbroudoux%2Fopen-policy-agent-k8s/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lbroudoux","download_url":"https://codeload.github.com/lbroudoux/open-policy-agent-k8s/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lbroudoux%2Fopen-policy-agent-k8s/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270796219,"owners_count":24647319,"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","status":"online","status_checked_at":"2025-08-16T02:00:11.002Z","response_time":91,"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":["gatekeeper","kubernetes","open-policy-agent","openshift"],"created_at":"2024-10-05T05:05:28.434Z","updated_at":"2025-08-17T01:33:20.405Z","avatar_url":"https://github.com/lbroudoux.png","language":"Open Policy Agent","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Open Policy Agent and GateKeeper explorations\n\nThis is a repository for exploration on [Open Policy Agent](https://www.openpolicyagent.org) and specifically the [Kubernetes part](https://www.openpolicyagent.org/docs/latest/kubernetes-introduction/) using Gatekeeper. On OpenShift only for the moment :wink: but will amend with Kubernetes stuffs if necessary.\n\n## Deploying GateKeeper\n\nGateKeeper is the Kubernetes admission controller that is enforcing policies when user creates, updates, deletes resources in the Kubernetes cluster.\n\n### Installation from README\n\n```sh\noc new-project gatekeeper-system\noc apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.2/deploy/gatekeeper.yaml -n gatekeeper-system\n```\n\n\u003e gatekeeper-system/gatekeeper-audit does not scale...\n\n```\nError creating: pods \"gatekeeper-audit-77bd6fc7d8-\" is forbidden: unable to validate against any security context constraint: [pod.metadata.annotations.container.seccomp.security.alpha.kubernetes.io/manager: Forbidden: seccomp may not be set spec.containers[0].securityContext.runAsUser: Invalid value: 1000: must be in the ranges: [1000610000, 1000619999] pod.metadata.annotations.container.seccomp.security.alpha.kubernetes.io/manager: Forbidden: seccomp may not be set]\n```\n\n\u003e gatekeeper-system/gatekeeper-controller-manager does not scale...\n\n```\nError creating: pods \"gatekeeper-controller-manager-6c7665cbd7-\" is forbidden: unable to validate against any security context constraint: [pod.metadata.annotations.container.seccomp.security.alpha.kubernetes.io/manager: Forbidden: seccomp may not be set spec.containers[0].securityContext.runAsUser: Invalid value: 1000: must be in the ranges: [1000610000, 1000619999] pod.metadata.annotations.container.seccomp.security.alpha.kubernetes.io/manager: Forbidden: seccomp may not be set]\n```\n\n\u003e It's because of the the seccomp annotation we have on both deployments.\n\n```yaml\nannotations:\n  container.seccomp.security.alpha.kubernetes.io/manager: runtime/default\n```\n\n### Fix that for OpenShift\n\n```sh\noc adm policy add-scc-to-user anyuid system:serviceaccount:gatekeeper-system:gatekeeper-admin\noc patch deployment/gatekeeper-audit --type json -p='[{\"op\": \"remove\", \"path\": \"/spec/template/metadata/annotations\"}]' -n gatekeeper-system\noc patch deployment/gatekeeper-controller-manager --type json -p='[{\"op\": \"remove\", \"path\": \"/spec/template/metadata/annotations\"}]' -n gatekeeper-system\n```\n\n### Review and adapt webhook configuration\n\n```sh\noc get ValidatingWebhookConfiguration/gatekeeper-validating-webhook-configuration -o json | jq .webhooks  \n[\n  {\n    \"admissionReviewVersions\": [\n      \"v1beta1\"\n    ],\n    \"clientConfig\": {\n      \"caBundle\": \"LS0...==\",\n      \"service\": {\n        \"name\": \"gatekeeper-webhook-service\",\n        \"namespace\": \"gatekeeper-system\",\n        \"path\": \"/v1/admit\",\n        \"port\": 443\n      }\n    },\n    \"failurePolicy\": \"Ignore\",\n    \"matchPolicy\": \"Exact\",\n    \"name\": \"validation.gatekeeper.sh\",\n    \"namespaceSelector\": {\n      \"matchExpressions\": [\n        {\n          \"key\": \"admission.gatekeeper.sh/ignore\",\n          \"operator\": \"DoesNotExist\"\n        }\n      ]\n    },\n    \"objectSelector\": {},\n    \"rules\": [\n      {\n        \"apiGroups\": [\n          \"*\"\n        ],\n        \"apiVersions\": [\n          \"*\"\n        ],\n        \"operations\": [\n          \"CREATE\",\n          \"UPDATE\"\n        ],\n        \"resources\": [\n          \"*\"\n        ],\n        \"scope\": \"*\"\n      }\n    ],\n    \"sideEffects\": \"None\",\n    \"timeoutSeconds\": 3\n  },\n  {\n    \"admissionReviewVersions\": [\n      \"v1beta1\"\n    ],\n    \"clientConfig\": {\n      \"caBundle\": \"LS0...==\",\n      \"service\": {\n        \"name\": \"gatekeeper-webhook-service\",\n        \"namespace\": \"gatekeeper-system\",\n        \"path\": \"/v1/admitlabel\",\n        \"port\": 443\n      }\n    },\n    \"failurePolicy\": \"Fail\",\n    \"matchPolicy\": \"Exact\",\n    \"name\": \"check-ignore-label.gatekeeper.sh\",\n    \"namespaceSelector\": {},\n    \"objectSelector\": {},\n    \"rules\": [\n      {\n        \"apiGroups\": [\n          \"\"\n        ],\n        \"apiVersions\": [\n          \"*\"\n        ],\n        \"operations\": [\n          \"CREATE\",\n          \"UPDATE\"\n        ],\n        \"resources\": [\n          \"namespaces\"\n        ],\n        \"scope\": \"*\"\n      }\n    ],\n    \"sideEffects\": \"None\",\n    \"timeoutSeconds\": 3\n  }\n]\n```\n\nPut a namespace selector to activate web hook for namespaces annotated with `lbroudoux.github.com/gatekeeper-active: true`:\n\n```\noc patch ValidatingWebhookConfiguration/gatekeeper-validating-webhook-configuration -p='{\"webhooks\":[{\"name\":\"validation.gatekeeper.sh\",\"namespaceSelector\":{\"matchExpressions\":[{\"key\":\"lbroudoux.github.com/gatekeeper-active\",\"operator\":\"In\",\"values\":[\"true\"]}]}}]}'\n```\n\n## Defining Constraint\n\nFirst create a `ConstraintTemplate`\n\n```yaml\napiVersion: templates.gatekeeper.sh/v1beta1\nkind: ConstraintTemplate\nmetadata:\n  name: k8sdeniedimageregistries\nspec:\n  crd:\n    spec:\n      names:\n        kind: K8sDeniedImageRegistries\n  targets:\n    - target: admission.k8s.gatekeeper.sh\n      rego: |\n        package denied_image_registries\n\n        default is_gatekeeper = false\n\n        # Checks whether the policy 'input' has came from Gatekeeper\n        is_gatekeeper {\n          has_field(input, \"review\")\n          has_field(input.review, \"object\")\n        }\n\n        # Check the obj contains a field\n        has_field(obj, field) {\n          obj[field]\n        }\n\n        # Get the input, as the input is not Gatekeeper based\n        object = input {\n          not is_gatekeeper\n        }\n\n        # Get the input.review.object, as the input is Gatekeeper based\n        object = input.review.object {\n          is_gatekeeper\n        }\n\n        # Set the .kind of the object we are currently working on\n        kind = object.kind\n\n        violation[{\"msg\": msg}] {\n          lower(kind) == \"pod\"\n          some i\n          image := object.spec.containers[i].image\n          not startswith(image, \"quay.io/\")\n          msg := sprintf(\"image '%v' comes from untrusted registry\", [image])\n          # msg := sprintf(\"REVIEW OBJECT: %v\", [input.review])\n        }\n```\n\nAnd apply it:\n\n```sh\noc create -f templates/k8sdeniedimageregistries.yaml -n gatekeeper-system \n```\n\nThen define a `Constraint`  of the CR type that was defined into previous `ConstraintTemplate`:\n\n```yaml\napiVersion: constraints.gatekeeper.sh/v1beta1\nkind: K8sDeniedImageRegistries\nmetadata:\n  name: pod-must-not-have-denied-registries\nspec:\n  match:\n    kinds:\n      - apiGroups: [\"\"]\n        kinds: [\"Pod\"]\n```\n\nAnd apply it:\n\n```sh\noc create -f constraints/k8sdeniedimageregistries.yaml -n gatekeeper-system\n```\n\nThen you can test creating a new namespace and creating violating resource:\n\n```sh\n$ oc new-project opa-test\n$ oc label namespace/opa-test lbroudoux.github.com/gatekeeper-active=true\n$ oc create -f tests/denied-pod.yaml -n opa-test\nError from server ([denied by pod-must-not-have-denied-registries] image 'docker.io/microcks/quarkus-api-pastry:latest' comes from untrusted registry): error when creating \"tests/denied-pod.yaml\": admission webhook \"validation.gatekeeper.sh\" denied the request: [denied by pod-must-not-have-denied-registries] image 'docker.io/microcks/quarkus-api-pastry:latest' comes from untrusted registry\n```\n\n## Using Confttest\n\nContest is a CLI tool allowing to test and validate your REGO policy definitions. Some policies are created within `policy/`  folder. \n\n```sh\nbrew tap instrumenta/instrumenta \nbrew install conftest\n```\n\nREGO files can be written for accepting both plain YAML files and Gatekeeper `Review` resources. Check some plumbing functions that allows that into REGO file: \n\n```\ndefault is_gatekeeper = false\n\n# Checks whether the policy 'input' has came from Gatekeeper\nis_gatekeeper {\n   has_field(input, \"review\")\n   has_field(input.review, \"object\")\n}\n# Check the obj contains a field\nhas_field(obj, field) {\n   obj[field]\n}\n# Get the input, as the input is not Gatekeeper based\nobject = input {\n   not is_gatekeeper\n}\n# Get the input.review.object, as the input is Gatekeeper based\nobject = input.review.object {\n   is_gatekeeper\n}\n```\n\nYou can check the `test-policies.sh` file to see how to use `conftest`.\n\n## Deploying Konstraint\n\n`konstraint` is a CLI tool that allow the generation of `ConstraintTemplate` and `Constraint` resources (as well as documentation) from REGO files. Just download `konstraint` from [Release v0.10.0 · plexsystems/konstraint · GitHub](https://github.com/plexsystems/konstraint/releases/tag/v0.10.0), place it into your `PATH` and don’t forget to `chmod a+x konstraint`\n\nCheck how `konstraint` creates templates, constraints and documentation:\n\n```sh\nkonstraint create\nkonstraint doc\n```\n\n## Managing OPA policies from Open Cluster Management (aka RHACM)\n\n[Open Cluster Management](https://open-cluster-management.io/) is the upstream community project bringing Multi-cluster Management features for Kubernetes and specially for OpenShift in a flavour called [Red Hat Advanced Cluster Management](https://access.redhat.com/documentation/en-us/red_hat_advanced_cluster_management_for_kubernetes/2.1/).\n\nWe can use RHCAM to ensure and enforce that our OPA Policies and GateKeeper Constraints are deployed on the many cluster we have to manage.\n\nFirst, start installing at least 2 clusters. One will be the Hub (as referenced into the [RHACM isntallation documentation](https://access.redhat.com/documentation/en-us/red_hat_advanced_cluster_management_for_kubernetes/2.1/html-single/install/index)) ; the other will be the managed one. Then, set up Open Cluster Management or RHACM as per the [doc](https://access.redhat.com/documentation/en-us/red_hat_advanced_cluster_management_for_kubernetes/2.1/html-single/install/index).\n\nOCM uses `Policy` alongside with `PlacementRule` to define to which clusters it should be applied and `PlacementBinding` to apply a placement rule to a policy. We'll simply wrap our GateKeeper `ConstraintTemplate` and `Constraints` with an OCM `Policy` like below:\n\n```yaml\napiVersion: policy.open-cluster-management.io/v1\nkind: Policy\nmetadata:\n  name: policy-denied-image-registries\n  annotations:\n    policy.open-cluster-management.io/categories: 'CA Security Assessment and Authorization'\nspec:\n  policy-templates:\n    - objectDefinition: \n        apiVersion: policy.open-cluster-management.io/v1\n        kind: ConfigurationPolicy\n        metadata:\n          name: policy-denied-image-registries-configuration\n        spec:\n          severity: high\n          namespaceSelector:\n            include: [\"gatekeeper-system\"]\n          object-templates:\n            - complianceType: musthave\n              objectDefinition:\n                apiVersion: templates.gatekeeper.sh/v1beta1\n                kind: ConstraintTemplate\n                metadata:\n                  name: k8sdeniedimageregistries\n                spec:\n                  crd:\n                    spec:\n                      names:\n                        kind: K8sDeniedImageRegistries\n                  targets:\n                    - target: admission.k8s.gatekeeper.sh\n                      rego: |-\n                        package denied_image_registries\n\n                        default is_gatekeeper = false\n\n                        # Checks whether the policy 'input' has came from Gatekeeper\n                        is_gatekeeper {\n                          has_field(input, \"review\")\n                          has_field(input.review, \"object\")\n                        }\n\n                        # Check the obj contains a field\n                        has_field(obj, field) {\n                          obj[field]\n                        }\n\n                        # Get the input, as the input is not Gatekeeper based\n                        object = input {\n                          not is_gatekeeper\n                        }\n\n                        # Get the input.review.object, as the input is Gatekeeper based\n                        object = input.review.object {\n                          is_gatekeeper\n                        }\n\n                        # Set the .kind of the object we are currently working on\n                        kind = object.kind\n\n                        violation[{\"msg\": msg}] {\n                          lower(kind) == \"pod\"\n                          some i\n                          image := object.spec.containers[i].image\n                          not startswith(image, \"quay.io/\")\n                          msg := sprintf(\"image '%v' comes from untrusted registry\", [image])\n                          # msg := sprintf(\"REVIEW OBJECT: %v\", [input.review])\n                        }\n          remediationAction: inform\n    - objectDefinition:\n        apiVersion: policy.open-cluster-management.io/v1\n        kind: ConfigurationPolicy\n        metadata:\n          name: policy-denied-image-registries-audit\n        spec:\n          severity: medium\n          namespaceSelector:\n            include: [\"gatekeeper-system\"]\n          object-templates:\n            - complianceType: musthave\n              objectDefinition:\n                apiVersion: constraints.gatekeeper.sh/v1beta1\n                kind: K8sDeniedImageRegistries\n                metadata:\n                  name: pod-must-not-have-denied-registries\n                spec:\n                  match:\n                    kinds:\n                      - apiGroups: [\"\"]\n                        kinds: [\"Pod\"]\n                status:\n                  totalViolations: 0\n    - objectDefinition:\n        apiVersion: policy.open-cluster-management.io/v1\n        kind: ConfigurationPolicy\n        metadata:\n          name: policy-denied-image-registries-admission\n        spec:\n          severity: low\n          object-templates:\n            - complianceType: mustnothave\n              objectDefinition:\n                apiVersion: v1\n                kind: Event\n                metadata:\n                  namespace: gatekeeper-system\n                  annotations:\n                    constraint_action: deny\n                    constraint_kind: K8sDeniedImageRegistries\n                    constraint_name: pod-must-not-have-denied-registries\n                    event_type: violation\n  remediationAction: inform\n  disabled: false\n---\napiVersion: policy.open-cluster-management.io/v1\nkind: PlacementBinding\nmetadata:\n  name: binding-policy-denied-image-registries\nplacementRef:\n  name: placement-policy-denied-image-registries\n  kind: PlacementRule\n  apiGroup: apps.open-cluster-management.io\nsubjects:\n- name: policy-denied-image-registries\n  kind: Policy\n  apiGroup: policy.open-cluster-management.io\n---\napiVersion: apps.open-cluster-management.io/v1\nkind: PlacementRule\nmetadata:\n  name: placement-policy-denied-image-registries\nspec:\n  clusterConditions:\n  - status: \"True\"\n    type: ManagedClusterConditionAvailable\n  clusterSelector:\n    matchExpressions:\n      - {key: environment, operator: In, values: [\"dev\"]}\n```\n\nSome explanations below:\n* The `Policy` is holding a first `ConfigurationPolicy` telling that managed cluster should have the `k8sdeniedimageregistries` `ConstraintTemplate` deployed,\n* Then it contains the `Constraint` definition as well as a check of the `status.totalViolations` field to raise alerts in case of there is existing violations,\n* Finally it contains a `ConfigurationPolicy` that is checking for violation events. This allow OCM to raise violation alerts when GateKeeper raise a `deny` event when someone tries creating a new rviolating esource on managed cluster.\n\nIn order to get this one working well, you'll need to turn on deny logs and events on GateKeeper on the managed server.\n\n```sh\noc patch deployment/gatekeeper-controller-manager --type json -p='[{\"op\": \"add\", \"path\": \"/spec/template/spec/containers/0/args/-\", \"value\": \"--log-denies\"}, {\"op\": \"add\", \"path\": \"/spec/template/spec/containers/0/args/-\", \"value\": \"--emit-admission-events\"}]' -n gatekeeper-system\n```\n\nWant to play with it? First login to the Hub server and create policies:\n\n```sh\noc create project policies\noc create ocm/policy-denied-image-registries.yaml -n policies\noc create ocm/policy-gatekeeper-deployment.yaml -n policies\n```\n\n\u003e `policy-gatekeeper-deployment.yaml` is another policy that validates that GateKeeper has been deployed on managed cluster.\n\nAt first, the `policy-denied-image-registries` will not be synced to the managed cluster so you'll need to `Enforce` it through the controls. Thus, GateKeeper constraints will be created on the managed cluster side.\n\n![ocm-opa-init.png](assets/ocm-opa-init.png)\n\nNow, login to the managed cluster and after having created a `opa-test` has described above, try creating a conflicting resource:\n\n```sh\n$ oc create -f tests/denied-pod.yaml -n opa-test                             \nError from server ([denied by pod-must-not-have-denied-registries] image 'docker.io/microcks/quarkus-api-pastry:latest' comes from untrusted registry): error when creating \"tests/denied-pod.yaml\": admission webhook \"validation.gatekeeper.sh\" denied the request: [denied by pod-must-not-have-denied-registries] image 'docker.io/microcks/quarkus-api-pastry:latest' comes from untrusted registry\n```\n\nDrill down to the violation details in RHACM and you'll get the reference of the `Event` that raise the violation alert:\n\n![ocm-opa-init.png](assets/ocm-opa-violation.png)\n\n## Useful resources\n\n* OPA `kube-mgmt` to Gatekeeper evolution: https://www.infracloud.io/blogs/opa-and-gatekeeper/\n* RHACM Integration explanations: https://ms-my.facebook.com/openshift/videos/368277450940388/?t=1949","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flbroudoux%2Fopen-policy-agent-k8s","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flbroudoux%2Fopen-policy-agent-k8s","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flbroudoux%2Fopen-policy-agent-k8s/lists"}