1"use strict";(self.webpackChunkopa_website=self.webpackChunkopa_website||[]).push([[35827],{28453:(e,n,s)=>{s.d(n,{R:()=>r,x:()=>o});var i=s(96540);const t={},a=i.createContext(t);function r(e){const n=i.useContext(a);return i.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:r(e.components),i.createElement(a.Provider,{value:n},e.children)}},72882:(e,n,s)=>{s.r(n),s.d(n,{assets:()=>d,contentTitle:()=>o,default:()=>h,frontMatter:()=>r,metadata:()=>i,toc:()=>l});const i=JSON.parse('{"id":"kubernetes/primer","title":"Policy Primer via Examples","description":"Read this page if you are new to Kubernetes admission control with OPA and want","source":"@site/docs/kubernetes/primer.md","sourceDirName":"kubernetes","slug":"/kubernetes/primer","permalink":"/docs/kubernetes/primer","draft":false,"unlisted":false,"tags":[],"version":"current","frontMatter":{"title":"Policy Primer via Examples"},"sidebar":"docsSidebar","previous":{"title":"Debugging Tips","permalink":"/docs/kubernetes/debugging"},"next":{"title":"Tutorial: Ingress Validation","permalink":"/docs/kubernetes/tutorial"}}');var t=s(74848),a=s(28453);const r={title:"Policy Primer via Examples"},o=void 0,d={},l=[{value:"Writing Policies",id:"writing-policies",level:2},{value:"Packages",id:"packages",level:3},{value:"Deny Rules",id:"deny-rules",level:3},{value:"Input Document",id:"input-document",level:3},{value:"Dot Notation",id:"dot-notation",level:3},{value:"Equality",id:"equality",level:3},{value:"Arrays",id:"arrays",level:3},{value:"Iteration",id:"iteration",level:3},{value:"Builtins",id:"builtins",level:3},{value:"Testing Policies",id:"testing-policies",level:2},{value:"Using Context in Policies",id:"using-context-in-policies",level:2},{value:"Detailed Admission Control Flow",id:"detailed-admission-control-flow",level:2}];function c(e){const n={a:"a",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,a.R)(),...e.components},{RunSnippet:s,SideBySideColumn:i,SideBySideContainer:r}=n;return s||u("RunSnippet",!0),i||u("SideBySideColumn",!0),r||u("SideBySideContainer",!0),(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)(n.p,{children:["Read this page if you are new to Kubernetes admission control with OPA and want\nto learn how to write policies for Kubernetes. It covers the version\nthat uses kube-mgmt. The ",(0,t.jsx)(n.a,{href:"https://open-policy-agent.github.io/gatekeeper/",children:"OPA Gatekeeper version"}),"\nhas its own docs."]}),"\n",(0,t.jsx)(n.h2,{id:"writing-policies",children:"Writing Policies"}),"\n",(0,t.jsx)(n.p,{children:"To get started, consider a common policy: ensure all images come from a\ntrusted registry."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",metastring:"showLineNumbers=true",children:'package kubernetes.admission\n\ndeny contains msg if {\n input.request.kind.kind == "Pod"\n image := input.request.object.spec.containers[_].image\n not startswith(image, "hooli.com/")\n msg := sprintf("image \'%v\' comes from untrusted registry", [image])\n}\n'})}),"\n",(0,t.jsx)(s,{id:"admission.rego"}),"\n",(0,t.jsx)(n.h3,{id:"packages",children:"Packages"}),"\n",(0,t.jsxs)(n.p,{children:["In ",(0,t.jsx)(n.strong,{children:"line 1"})," the ",(0,t.jsx)(n.code,{children:"package kubernetes.admission"})," declaration gives the (hierarchical) name ",(0,t.jsx)(n.code,{children:"kubernetes.admission"})," to the rules in the remainder of the policy. The default installation of OPA as an admission controller assumes your rules are in the package ",(0,t.jsx)(n.code,{children:"kubernetes.admission"}),"."]}),"\n",(0,t.jsx)(n.h3,{id:"deny-rules",children:"Deny Rules"}),"\n",(0,t.jsxs)(n.p,{children:["Typically ",(0,t.jsx)(n.code,{children:"deny"})," rules are used for admission control; their order does not change the result. Rego rules can implement all sorts of different logic, but for admission control starting with ",(0,t.jsx)(n.code,{children:"deny"})," rules is recommended. In ",(0,t.jsx)(n.strong,{children:"line 2"}),", the ",(0,t.jsx)(n.em,{children:"head"})," of the rule ",(0,t.jsx)(n.code,{children:"deny contains msg if"})," says that the admission control request should be rejected and the user handed the error message ",(0,t.jsx)(n.code,{children:"msg"})," if the conditions in the ",(0,t.jsx)(n.em,{children:"body"})," (the statements between the ",(0,t.jsx)(n.code,{children:"{}"}),") are true."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"deny"})," is the ",(0,t.jsx)(n.em,{children:"set"})," of error messages that should be returned to the user. Each rule you write adds to that set of error messages."]}),"\n",(0,t.jsx)(n.p,{children:"For example, suppose you tried to create the Pod below with
1nginx and mysql images."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"kind: Pod\napiVersion: v1\nmetadata:\n name: myapp\nspec:\n containers:\n - image: nginx\n name: nginx-frontend\n - image: mysql\n name: mysql-backend\n"})}),"\n",(0,t.jsx)(n.p,{children:"The admission review request to be sent to OPA would look like this:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",metastring:'title="input.json"',children:'{\n "kind": "AdmissionReview",\n "request": {\n "kind": {\n "kind": "Pod",\n "version": "v1"\n },\n "object": {\n "metadata": {\n "name": "myapp"\n },\n "spec": {\n "containers": [\n {\n "image": "nginx",\n "name": "nginx-frontend"\n },\n {\n "image": "mysql",\n "name": "mysql-backend"\n }\n ]\n }\n }\n }\n}\n'})}),"\n",(0,t.jsx)(s,{id:"input.json"}),"\n",(0,t.jsxs)(n.p,{children:["When the ",(0,t.jsx)(n.code,{children:"deny"})," rule is evaluated with the input above:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:"package example\n\nimport data.kubernetes.admission\n\nresult := admission.deny\n"})}),"\n",(0,t.jsx)(s,{files:"#input.json #admission.rego",command:"data.example.result"}),"\n",(0,t.jsx)(n.h3,{id:"input-document",children:"Input Document"}),"\n",(0,t.jsxs)(n.p,{children:["In OPA, ",(0,t.jsx)(n.code,{children:"input"})," is a reserved, global variable whose value is the Kubernetes AdmissionReview object that the API server hands to any admission control webhook."]}),"\n",(0,t.jsxs)(n.p,{children:["AdmissionReview objects have many fields. The rule above uses ",(0,t.jsx)(n.code,{children:"input.request.kind"}),", which includes the usual group/version/kind information. The rule also uses ",(0,t.jsx)(n.code,{children:"input.request.object"}),", which is the YAML that the user provided to ",(0,t.jsx)(n.code,{children:"kubectl"})," (augmented with defaults, timestamps, etc.). The full ",(0,t.jsx)(n.code,{children:"input"})," object is 50+ lines of YAML, so only the relevant parts are shown below."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"apiVersion: admission.k8s.io/v1\nkind: AdmissionReview\nrequest:\n kind:\n group:\n kind: Pod\n version: v1\n object:\n metadata:\n name: myapp\n spec:\n containers:\n - image: nginx\n name: nginx-frontend\n - image: mysql\n name: mysql-backend\n"})}),"\n",(0,t.jsx)(n.h3,{id:"dot-notation",children:"Dot Notation"}),"\n",(0,t.jsxs)(n.p,{children:["In line 3 ",(0,t.jsx)(n.code,{children:'input.request.kind.kind == "Pod"'}),", the expression ",(0,t.jsx)(n.code,{children:"input.request.kind.kind"})," does the obvious thing: it descends through the YAML hierarchy. The dot (.) operator never throws any errors; if the path does not exist the value of the expression is ",(0,t.jsx)(n.code,{children:"undefined"}),"."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:"package example\n\nresult := input.request.kind\n"})}),"\n",(0,t.jsx)(s,{files:"#input.json",command:"data.example.result"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:"package example\n\nresult := input.request.kind.kind\n"})}),"\n",(0,t.jsx)(s,{files:"#input.json",command:"data.example.result"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:"package example\n\nresult := input.request.object.spec.containers\n"})}),"\n",(0,t.jsx)(s,{files:"#input.json",command:"data.example.result"}),"\n",(0,t.jsx)(n.h3,{id:"equality",children:"Equality"}),"\n",(0,t.jsx)(n.p,{children:"Lines 3, 4, 6 all use a form of equality. There are 3 forms of equality in OPA."}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"x := 7"})," declares a local variable ",(0,t.jsx)(n.code,{children:"x"})," and assigns it a value of 7. The compiler throws an error if ",(0,t.jsx)(n.code,{children:"x"})," already has a value."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"x == 7"})," returns true if ",(0,t.jsx)(n.code,{children:"x"})," has a value of 7. The compiler throws an error if ",(0,t.jsx)(n.code,{children:"x"})," has no value."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"x = 7"})," either assigns the value 7 to ",(0,t.jsx)(n.code,{children:"x"})," if ",(0,t.jsx)(n.code,{children:"x"})," has no value or compares ",(0,t.jsx)(n.code,{children:"x"}),"'s value to 7 if it has a value. The compiler never throws an error."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["The recommendation for rule-writing is to use ",(0,t.jsx)(n.code,{children:":="})," and ",(0,t.jsx)(n.code,{children:"=="})," wherever possible. Rules written with ",(0,t.jsx)(n.code,{children:":="})," and ",(0,t.jsx)(n.code,{children:"=="})," are easier to write and to read. ",(0,t.jsx)(n.code,{children:"="}
1)," is invaluable in more advanced use cases, and outside of rules is the only supported form of equality."]}),"\n",(0,t.jsx)(n.h3,{id:"arrays",children:"Arrays"}),"\n",(0,t.jsxs)(n.p,{children:["Lines 4-5 find images in the Pod that don't come from the trusted registry. To do that, they use the ",(0,t.jsx)(n.code,{children:"[]"})," operator, which does what you expect: index into the array."]}),"\n",(0,t.jsx)(n.p,{children:"Continuing the example from earlier:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:"package example\n\nresult := input.request.object.spec.containers[0]\n"})}),"\n",(0,t.jsx)(s,{files:"#input.json",command:"data.example.result"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:"package example\n\nresult := input.request.object.spec.containers[0].image\n"})}),"\n",(0,t.jsx)(s,{files:"#input.json",command:"data.example.result"}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.code,{children:"[]"})," operators let you use variables to index into the array as well."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:"package example\n\nresult contains c if {\n i := 0\n c := input.request.object.spec.containers[i]\n}\n"})}),"\n",(0,t.jsx)(s,{files:"#input.json",command:"data.example.result"}),"\n",(0,t.jsx)(n.h3,{id:"iteration",children:"Iteration"}),"\n",(0,t.jsx)(n.p,{children:"The containers array has an unknown number of elements, so to implement an image registry check you need to iterate over them. Iteration in OPA requires no new syntax. In fact, OPA is always iterating--it's always searching for all variable assignments that make the conditions in the rule true. It's just that sometimes the search is so easy people don't think of it as iteration/search."}),"\n",(0,t.jsxs)(n.p,{children:["To iterate over the indexes in the ",(0,t.jsx)(n.code,{children:"input.request.object.spec.containers"})," array, you just put a variable that has no value in for the index. OPA will do what it always does: find values for that variable that make the conditions true."]}),"\n",(0,t.jsx)(n.p,{children:"OPA detects when there will be multiple answers and displays all the results in a table."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:"package example\n\nresult contains c if {\n some j\n c := input.request.object.spec.containers[j]\n}\n"})}),"\n",(0,t.jsx)(s,{files:"#input.json",command:"data.example.result"}),"\n",(0,t.jsxs)(n.p,{children:["Often you don't want to invent new variable names for iteration. OPA provides the special anonymous variable ",(0,t.jsx)(n.code,{children:"_"})," for exactly that reason. So in line (4) ",(0,t.jsx)(n.code,{children:"image := input.request.object.spec.containers[_].image"})," finds all the images in the containers array and assigns each to the ",(0,t.jsx)(n.code,{children:"image"})," variable one at a time."]}),"\n",(0,t.jsx)(n.h3,{id:"builtins",children:"Builtins"}),"\n",(0,t.jsxs)(n.p,{children:["On line 5 the ",(0,t.jsx)(n.em,{children:"builtin"})," ",(0,t.jsx)(n.code,{children:"startswith"})," checks if one string is a prefix of the other. The builtin ",(0,t.jsx)(n.code,{children:"sprintf"})," on line 6 formats a string with arguments. OPA has 150+ builtins detailed in ",(0,t.jsx)(n.a,{href:"../policy-reference/#built-in-functions",children:"the Policy Reference"}),".\nBuiltins let you analyze and manipulate:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Numbers, Strings, Regexs, Networks"}),"\n",(0,t.jsx)(n.li,{children:"Aggregates, Arrays, Sets"}),"\n",(0,t.jsx)(n.li,{children:"Types"}),"\n",(0,t.jsx)(n.li,{children:"Encodings (base64, YAML, JSON, URL, JWT)"}),"\n",(0,t.jsx)(n.li,{children:"Time"}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"testing-policies",children:"Testing Policies"}),"\n",(0,t.jsxs)(n.p,{children:["When you write policies, you should use the OPA unit-test framework ",(0,t.jsx)(n.em,{children:"before"})," sending the policies out into the OPA that is running on your cluster. The debugging process will be much quicker and effective. Here's an example test for the policy from the last section."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:'package kubernetes.test_admission # line 1\n\nimport data.kubernetes.admission # line 2\n\ntest_image_safety if { # line 3\n unsafe_image := { # line 4\n "request": {\n "kind": {"kind": "Pod"},\n "object": {\n "spec": {\n "containers": [\n {"image": "hooli.com/nginx"},\n {"image": "busybox"}\n ]\n }\n }\n }\n }\n expected := "image \'busybox\' comes from untrusted registry"\n admission.deny[expected] with input as unsafe_image # line 5\n}\n'})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Different Package"}),". On line 1 the ",(0,t.jsx)(n.code,{children:"package"})," directive puts these tests in a different package than admission control policy itself. This is the recommended best practice."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Import"}),". On line 2 ",(0,t.jsx)(n.code,{children:"import data.kubernetes.admission"})," allows us to reference the admission control policy using the name ",(0,t.jsx)(n.code,{children:"admission"})," everywhere in the test package. ",(0,t.jsx)(n.code,{children:"import"})," is not strictly necessary--it sets up an alias; you could instead reference ",(0,t.jsx)(n.code,{children:"data.kubernetes.admission"})," inside the rules."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Unit Test"}),". On line 3 ",(0,t.jsx)(n.code,{children:"test_image_safety"})," defines a unittest. If the rule evaluates to true the test passes; otherwise it fails. When you use the OPA test runner, anything in any package starting with ",(0,t.jsx)(n.code,{children:"test"})," is treated as a test."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Assignment"}),". On line 4 ",(0,t.jsx)(n.code,{children:"unsafe_image"})," is the input to use for the test. Ideally this would be a real AdmissionReview object, though those are so long that in this example, a hand-rolled partial input is used."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Dot for packages"}),". On line 5, the Dot operator is used on a package. ",(0,t.jsx)(n.code,{children:"admission.deny[expected]"})," runs the ",(0,t.jsx)(n.code,{children:"deny"})," rule(s) in package ",(0,t.jsx)(n.code,{children:"admission"})," and checks if the message is contained in the set defined by ",(0,t.jsx)(n.code,{children:"deny"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Test Input"}),". Also on line 5 the stanza ",(0,t.jsx)(n.code,{children:"with input as unsafe_image"})," sets the value of ",(0,t.jsx)(n.code,{children:"input"})," to be ",(0,t.jsx)(n.code,{children:"unsafe_image"})," while evaluating ",(0,t.jsx)(n.code,{children:"admission.deny[expected]"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Running Tests"}),". If you've created the files ",(0,t.jsx)(n.em,{children:"image-safety.rego"})," and ",(0,t.jsx)(n.em,{children:"test-image-safety.rego"})," in the current directory then you run the tests by naming the files explicitly as shown below or by handing the ",(0,t.jsx)(n.code,{children:"opa test"})," command the directory (and subdirectories) of files to load: ",(0,t.jsx)(n.code,{children:"opa test ."})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"$ opa test image-safety.rego test-image-safety.rego\nPASS: 1/1\n"})}),"\n",(0,t.jsx)(n.h2,{id:"using-context-in-policies",children:"Using Context in Policies"}),"\n",(0,t.jsx)(n.p,{children:"The image-repository example shows an example where you can make a policy decision using just the one JSON/YAML file describing the resource in question. But sometimes you need to know what other resources exist in the cluster to make an allow/deny decision."}),"\n",(0,t.jsx)(n.p,{children:"For example, it\u2019s possible to accidentally configure two Kubernetes ingresses so that one steals traffic from the other. The policy that prevents conflicting ingresses needs to compare the ingress that\u2019s being created/updated with all of the existing ingresses. Just knowing the new/updated ingress isn't enough information to make an allow/deny decision."}),"\n",(0,t.jsxs)(n.p,{children:["Below is a partial example of the input OPA sees when someone creates an ingress. To avoid conflicts, the goal is to prevent two ingresses from having the same ",(0,t.jsx)(n.code,{children:"request.object.spec.rules.host"}),". If OPA has only this one ingress configuration it doesn't have enough information to make an allow/deny decision; it also needs the configurations for all of the existing ingresses."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"apiVersion: admission.k8s.io/v1\nkind: AdmissionReview\nrequest:\n kind:\n group: networking.k8s.io\n kind: Ingress\n version: v1\n object:\n metadata:\n name: prod\n spec:\n rules:\n - host: initech.c
1om\n http:\n paths:\n - path: /finance\n pathType: Prefix\n backend:\n service:\n name: banking\n port:\n number: 443\n"})}),"\n",(0,t.jsx)(n.p,{children:"To avoid conflicting ingresses, you write a policy like the one that follows."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:'package kubernetes.admission\n\ndeny contains msg if {\n some namespace, name\n input.request.kind.kind == "Ingress" # line 1\n newhost := input.request.object.spec.rules[_].host # line 2\n oldhost := data.kubernetes.ingresses[namespace][name].spec.rules[_].host # line 3\n newhost == oldhost # line 4\n input.request.object.metadata.namespace != namespace # line 5\n input.request.object.metadata.name != name # line 6\n msg := sprintf("ingress host conflicts with ingress %v/%v", [namespace, name]) # line 7\n}\n'})}),"\n",(0,t.jsx)(n.p,{children:"The first part of the rule you already understand:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["Line (1) checks if the ",(0,t.jsx)(n.code,{children:"input"})," is an Ingress"]}),"\n",(0,t.jsxs)(n.li,{children:["Line (2) iterates over all the rules in the ",(0,t.jsx)(n.code,{children:"input"})," ingress and looks up the ",(0,t.jsx)(n.code,{children:"host"})," field for each of its rules."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Existing Kubernetes Resources"})," Line (3) iterates over ingresses that already exist in Kubernetes. ",(0,t.jsx)(n.code,{children:"data"})," is a global variable where (among other things) OPA has a record of the current resources inside Kubernetes. The line ",(0,t.jsx)(n.code,{children:"oldhost := data.kubernetes.ingresses[namespace][name].spec.rules[_].host"})," finds all ingresses in all namespaces, iterates over all the ",(0,t.jsx)(n.code,{children:"rules"})," inside each of those and assigns the ",(0,t.jsx)(n.code,{children:"host"})," field to the variable ",(0,t.jsx)(n.code,{children:"oldhost"}),". Whenever ",(0,t.jsx)(n.code,{children:"newhost == oldhost"}),", there's a conflict, and the OPA rule includes an appropriate error message into the ",(0,t.jsx)(n.code,{children:"deny"})," set."]}),"\n",(0,t.jsxs)(n.p,{children:["In this case the rule uses explicit variable names ",(0,t.jsx)(n.code,{children:"namespace"})," and ",(0,t.jsx)(n.code,{children:"name"})," for iteration so that it can use those variables again when constructing the error message in line (7)."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Schema Differences"}),". Both ",(0,t.jsx)(n.code,{children:"input"})," and ",(0,t.jsx)(n.code,{children:"data.kubernetes.ingresses[namespace][name]"})," represent ingresses, but they do it differently."]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"input"})," is a Kubernetes AdmissionReview object. It includes several fields in addition to the Kubernetes Ingress object itself."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"data.kubernetes.ingresses[namespace][name]"})," is a native Kubernetes Ingress object as returned by the API."]}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Here are two examples."}),"\n",(0,t.jsxs)(r,{children:[(0,t.jsx)(i,{children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:'title="data.kubernetes.ingresses[namespace][name]"',children:"apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n name: prod\nspec:\n rules:\n - host: initech.com\n http:\n paths:\n - path: /finance\n pathType: Prefix\n backend:\n service:\n name: banking\n port:\n number: 443\n"})})}),(0,t.jsx)(i,{children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:'title="admission_review.yaml"',children:"apiVersion: admission.k8s.io/v1\nkind: AdmissionReview\nrequest:\n kind:\n group: networking.k8s.io\n kind: Ingress\n version: v1\n operation: CREATE\n userInfo:\n groups:\n username: alice\n object:\n metadata:\n name: prod\n spec:\n rules:\n - host: initech.c
1om\n http:\n paths:\n - path: /finance\n pathType: Prefix\n backend:\n service:\n name: banking\n port:\n number: 443\n"})})})]}),"\n",(0,t.jsx)(n.h2,{id:"detailed-admission-control-flow",children:"Detailed Admission Control Flow"}),"\n",(0,t.jsxs)(n.p,{children:["This section provides a detailed explanation of the admission control flow\nintroduced in the ",(0,t.jsx)(n.a,{href:"..",children:"Introduction"})," page."]}),"\n",(0,t.jsxs)(n.p,{children:["It starts with someone (or something) running ",(0,t.jsx)(n.code,{children:"kubectl"})," (or sending a request to\nthe API server.) For example, a user might run ",(0,t.jsx)(n.code,{children:"kubectl create -f pod.yaml"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:'title="pod.yaml"',children:"kind: Pod\napiVersion: v1\nmetadata:\n name: nginx\n labels:\n app: nginx\nspec:\n containers:\n - image: nginx\n name: nginx\n"})}),"\n",(0,t.jsxs)(n.p,{children:["When the request reaches the API server it's authenticated and authorized and\nprocessed by the admission controllers. When the API server's Webhook admission\ncontroller executes, the API server sends a webhook request to OPA containing an\n",(0,t.jsx)(n.strong,{children:"AdmissionReview"})," object."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",metastring:'title="admission_review.yaml"',children:'apiVersion: admission.k8s.io/v1\nkind: AdmissionReview\nrequest:\n kind:\n group: ""\n kind: Pod\n version: v1\n namespace: opa\n object:\n metadata:\n creationTimestamp: "2018-10-27T02:12:20Z"\n labels:\n app: nginx\n name: nginx\n namespace: opa\n uid: bbfee96d-d98d-11e8-b280-080027868e77\n spec:\n containers:\n - image: nginx\n imagePullPolicy: Always\n name: nginx\n resources: {}\n terminationMessagePath: "/dev/termination-log"\n terminationMessagePolicy: File\n volumeMounts:\n - mountPath: "/var/run/secrets/kubernetes.io/serviceaccount"\n name: default-token-tm9v8\n readOnly: true\n dnsPolicy: ClusterFirst\n restartPolicy: Always\n schedulerName: default-scheduler\n securityContext: {}\n serviceAccount: default\n serviceAccountName: default\n terminationGracePeriodSeconds: 30\n tolerations:\n - effect: NoExecute\n key: node.kubernetes.io/not-ready\n operator: Exists\n tolerationSeconds: 300\n - effect: NoExecute\n key: node.kubernetes.io/unreachable\n operator: Exists\n tolerationSeconds: 300\n volumes:\n - name: default-token-tm9v8\n secret:\n secretName: default-token-tm9v8\n status:\n phase: Pending\n qosClass: BestEffort\n oldObject:\n operation: CREATE\n resource:\n group: ""\n resource: pods\n version: v1\n uid: 8d836dfd-e0c0-4490-93ba-85ed4a04261e\n userInfo:\n groups:\n - system:masters\n - system:authenticated\n username: minikube-user\n'})}),"\n",(0,t.jsxs)(n.p,{children:["Typically the API server is configured (via ",(0,t.jsx)(n.code,{children:"ValidatingWebhookConfiguration"})," or\n",(0,t.jsx)(n.code,{children:"MutatingWebhookConfiguration"})," objects) to query OPA without providing the name\nof a decision. For example:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-http",children:"POST / HTTP/1.1\nContent-Type: application/json\n"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",children:'{\n "apiVersion": "admission.k8s.io/v1",\n "kind": "AdmissionReview",\n "request": ...\n}\n'})}),"\n",(0,t.jsxs)(n.p,{children:["When OPA receives the webhook request, it binds the payload to the ",(0,t.jsx)(n.code,{children:"input"}),"\ndocument and generates the default decision: ",(0,t.jsx)(n.code,{children:"system.main"}),". The ",(0,t.jsx)(n.code,{children:"system.main"}),"\ndecision is defined by a rule that evaluates all of the admission control\npolicies that have been loaded into OPA."]}),"\n",(0,t.jsxs)(n.p,{children:["As the administrator responsible for deploying OPA, you have full control over\nthe ",(0,t.jsx)(n.code,{children:"system.main"})," decision (i.e., it is just another Rego policy.) A basic\nimplementation of the ",(0,t.jsx)(n.code,{children:"system.main"})," policy evaluates all deny rules that\nhave been loaded into OPA and unions the results:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:'package system\n\nimport data.kubernetes.admission\n\nmain := {\n "apiVersion": "admission.k8s.io/v1",\n "kind": "AdmissionReview",\n "response": response,\n}\n\ndefault uid := ""\n\nuid := input.request.uid\n\nresponse := {\n "allowed": false,\n "uid": uid,\n "status": {"message": reason},\n} if {\n reason := concat(", ", admission.deny)\n reason != ""\n}\n\nelse := {"allowed": true, "uid": uid}\n'})}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.code,{children:"system.main"})," policy MUST generate an ",(0,t.jsx)(n.strong,{children:"AdmissionReview"}
1)," object containing\na response that the API server can interpret. If the request should be allowed,\nthe ",(0,t.jsx)(n.code,{children:"response.allowed"})," field should be true. Otherwise, the ",(0,t.jsx)(n.code,{children:"response.allowed"}),"\nfield should be set to ",(0,t.jsx)(n.code,{children:"false"})," and the ",(0,t.jsx)(n.code,{children:"response.status.message"})," field should be\nset to include an error message that indicates why the request is being\nrejected. The error message will be returned to the API server caller (e.g., the\nuser running ",(0,t.jsx)(n.code,{children:"kubectl"}),"). Often the error message is the concatenation of all the\nmessages in the ",(0,t.jsx)(n.code,{children:"deny"})," set defined above."]}),"\n",(0,t.jsx)(n.p,{children:"For example, with the input and Image Registry Safety examples above, the\nresponse from OPA would be:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'apiVersion: admission.k8s.io/v1\nkind: AdmissionReview\nresponse:\n uid: 8d836dfd-e0c0-4490-93ba-85ed4a04261e\n allowed: false\n status:\n message: "image fails to come from trusted registry: nginx"\n'})}),"\n",(0,t.jsxs)(n.p,{children:["For more detail on how Kubernetes Admission Control works, see ",(0,t.jsx)(n.a,{href:"https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/",children:"this blog post"}),"\non kubernetes.io."]})]})}function h(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(c,{...e})}):c(e)}function u(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.