diff --git a/docs/RELEASE.md b/.docs/RELEASE.md similarity index 100% rename from docs/RELEASE.md rename to .docs/RELEASE.md diff --git a/manifests/cert-manager/cluster-issuer.yaml b/.manifests/cert-manager/cluster-issuer.yaml similarity index 100% rename from manifests/cert-manager/cluster-issuer.yaml rename to .manifests/cert-manager/cluster-issuer.yaml diff --git a/manifests/policies/demo-policy.example.com.yaml b/.manifests/policies/demo-policy.example.com.yaml similarity index 100% rename from manifests/policies/demo-policy.example.com.yaml rename to .manifests/policies/demo-policy.example.com.yaml diff --git a/Makefile b/Makefile index cf66f5a..710fa29 100644 --- a/Makefile +++ b/Makefile @@ -318,7 +318,7 @@ install-cert-manager: $(HELM) install-cluster-issuer: ## Install cert-manager cluster issuer install-cluster-issuer: @echo Install cert-manager cluster issuer... >&2 - @kubectl apply -f manifests/cert-manager/cluster-issuer.yaml + @kubectl apply -f .manifests/cert-manager/cluster-issuer.yaml ######### # ISTIO # @@ -353,7 +353,6 @@ install-kyverno-sidecar-injector: $(HELM) .PHONY: install-kyverno-authz-server install-kyverno-authz-server: ## Install kyverno-authz-server chart install-kyverno-authz-server: kind-load-image -install-kyverno-authz-server: codegen-crds install-kyverno-authz-server: $(HELM) @echo Install CRDs... >&2 @kubectl apply -f $(CRDS_PATH) diff --git a/docs/quick_start.yaml b/docs/quick_start.yaml deleted file mode 100644 index 9fe3746..0000000 --- a/docs/quick_start.yaml +++ /dev/null @@ -1,212 +0,0 @@ -# Application Deployment with kyverno-envoy-plugin and Envoy sidecars. -apiVersion: apps/v1 -kind: Deployment -metadata: - name: testapp -spec: - replicas: 1 - selector: - matchLabels: - app: testapp - template: - metadata: - labels: - app: testapp - spec: - initContainers: - - name: proxy-init - image: sanskardevops/proxyinit:latest - # Configure the iptables bootstrap script to redirect traffic to the - # Envoy proxy on port 8000, specify that Envoy will be running as user - # 1111, and that we want to exclude port 8181 from the proxy for the Kyverno health checks. - # These values must match up with the configuration - # defined below for the "envoy" and "kyverno-envoy-plugin" containers. - args: ["-p", "7000", "-u", "1111", -w, "8181"] - securityContext: - capabilities: - add: - - NET_ADMIN - runAsNonRoot: false - runAsUser: 0 - containers: - - name: test-application - image: sanskardevops/test-application:0.0.1 - ports: - - containerPort: 8080 - - name: envoy - image: envoyproxy/envoy:v1.30-latest - securityContext: - runAsUser: 1111 - imagePullPolicy: IfNotPresent - volumeMounts: - - readOnly: true - mountPath: /config - name: proxy-config - args: - - "envoy" - - "--config-path" - - "/config/envoy.yaml" - - name: kyverno-envoy-plugin - image: sanskardevops/plugin:0.0.34 - imagePullPolicy: IfNotPresent - ports: - - containerPort: 8181 - - containerPort: 9000 - volumeMounts: - - readOnly: true - mountPath: /policies - name: policy-files - args: - - "serve" - - "--policy=/policies/policy.yaml" - - "--address=:9000" - - "--healthaddress=:8181" - livenessProbe: - httpGet: - path: /health - scheme: HTTP - port: 8181 - initialDelaySeconds: 5 - periodSeconds: 5 - readinessProbe: - httpGet: - path: /health - scheme: HTTP - port: 8181 - initialDelaySeconds: 5 - periodSeconds: 5 - volumes: - - name: proxy-config - configMap: - name: proxy-config - - name: policy-files - configMap: - name: policy-files ---- -# Envoy Config with External Authorization filter that will query kyverno-envoy-plugin. -apiVersion: v1 -kind: ConfigMap -metadata: - name: proxy-config -data: - envoy.yaml: | - static_resources: - listeners: - - address: - socket_address: - address: 0.0.0.0 - port_value: 7000 - filter_chains: - - filters: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - codec_type: auto - stat_prefix: ingress_http - route_config: - name: local_route - virtual_hosts: - - name: backend - domains: - - "*" - routes: - - match: - prefix: "/" - route: - cluster: service - http_filters: - - name: envoy.ext_authz - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz - transport_api_version: V3 - with_request_body: - max_request_bytes: 8192 - allow_partial_message: true - failure_mode_allow: false - grpc_service: - google_grpc: - target_uri: 127.0.0.1:9000 - stat_prefix: ext_authz - timeout: 0.5s - - name: envoy.filters.http.router - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router - clusters: - - name: service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: 127.0.0.1 - port_value: 8080 - admin: - access_log_path: "/dev/null" - address: - socket_address: - address: 0.0.0.0 - port_value: 8001 - layered_runtime: - layers: - - name: static_layer_0 - static_layer: - envoy: - resource_limits: - listener: - example_listener_name: - connection_limit: 10000 - overload: - global_downstream_max_connections: 50000 ---- -# Example policy to enforce into kyverno-envoy-plugin sidecars. -apiVersion: v1 -kind: ConfigMap -metadata: - name: policy-files -data: - policy.yaml: | - apiVersion: json.kyverno.io/v1alpha1 - kind: ValidatingPolicy - metadata: - name: checkrequest - spec: - rules: - - name: deny-guest-request-at-post - assert: - any: - - message: "POST method calls at path /book are not allowed to guests users" - check: - request: - http: - method: POST - headers: - authorization: - (split(@, ' ')[1]): - (jwt_decode(@ , 'secret').payload.role): admin - path: /book - - message: "GET method call is allowed to both guest and admin users" - check: - request: - http: - method: GET - headers: - authorization: - (split(@, ' ')[1]): - (jwt_decode(@ , 'secret').payload.role): admin - path: /book - - message: "GET method call is allowed to both guest and admin users" - check: - request: - http: - method: GET - headers: - authorization: - (split(@, ' ')[1]): - (jwt_decode(@ , 'secret').payload.role): guest - path: /book - \ No newline at end of file diff --git a/pkg/authz/cel/libs/jwt/lib.go b/pkg/authz/cel/libs/jwt/lib.go index da225dc..925b049 100644 --- a/pkg/authz/cel/libs/jwt/lib.go +++ b/pkg/authz/cel/libs/jwt/lib.go @@ -54,11 +54,15 @@ func decode(token ref.Val, key ref.Val) ref.Val { if !ok { return types.MaybeNoSuchOverloadErr(key) } - out, err := jwt.Parse(string(t), func(*jwt.Token) (any, error) { + parsed, err := jwt.Parse(string(t), func(*jwt.Token) (any, error) { return []byte(k), nil }) if err != nil { - return types.WrapErr(err) + return types.DefaultTypeAdapter.NativeToValue(nil) } - return Token{Token: out} + return types.DefaultTypeAdapter.NativeToValue(map[string]any{ + "header": parsed.Header, + "claims": parsed.Claims.(jwt.MapClaims), + "valid": parsed.Valid, + }) } diff --git a/pkg/policy/provider.go b/pkg/policy/provider.go index 34289f5..e8c2364 100644 --- a/pkg/policy/provider.go +++ b/pkg/policy/provider.go @@ -69,6 +69,7 @@ func (r *policyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr } compiled, err := r.compiler.Compile(policy) if err != nil { + fmt.Println(err) // TODO: not sure we should retry it return ctrl.Result{}, err } diff --git a/website/docs/cel-extensions/jwt.md b/website/docs/cel-extensions/jwt.md index 87c5271..92fda07 100644 --- a/website/docs/cel-extensions/jwt.md +++ b/website/docs/cel-extensions/jwt.md @@ -22,7 +22,7 @@ kind: AuthorizationPolicy metadata: name: demo spec: - failurePolicy: Ignore + failurePolicy: Fail variables: - name: token expression: > diff --git a/website/docs/jp.md b/website/docs/jp.md deleted file mode 100644 index ee0b890..0000000 --- a/website/docs/jp.md +++ /dev/null @@ -1,15 +0,0 @@ -# Overview - -`kyverno-json` uses [JMESPath community edition](https://jmespath.site/), a modern JMESPath implementation with lexical scopes support. - -The current *payload*, *policy* and *rule* are always available using the following builtin bindings: - -| Binding | Usage | -|---|---| -| `$payload` | Current payload being analysed | -| `$policy` | Current policy being executed | -| `$rule` | Current rule being evaluated | - -!!! warning - - No protection is made to prevent you from overriding those bindings. diff --git a/website/docs/jp/functions.md b/website/docs/jp/functions.md deleted file mode 100644 index 398c684..0000000 --- a/website/docs/jp/functions.md +++ /dev/null @@ -1,108 +0,0 @@ -# Functions - -## built-in functions - -| Name | Signature | -|---|---| -| abs | `abs(number)` | -| avg | `avg(array[number])` | -| ceil | `ceil(number)` | -| contains | `contains(array\|string, any)` | -| ends_with | `ends_with(string, string)` | -| find_first | `find_first(string, string, number, number)` | -| find_last | `find_last(string, string, number, number)` | -| floor | `floor(number)` | -| from_items | `from_items(array[array])` | -| group_by | `group_by(array, expref)` | -| items | `items(object)` | -| join | `join(string, array[string])` | -| keys | `keys(object)` | -| length | `length(string\|array\|object)` | -| lower | `lower(string)` | -| map | `map(expref, array)` | -| max | `max(array[number]\|array[string])` | -| max_by | `max_by(array, expref)` | -| merge | `merge(object)` | -| min | `min(array[number]\|array[string])` | -| min_by | `min_by(array, expref)` | -| not_null | `not_null(any)` | -| pad_left | `pad_left(string, number, string)` | -| pad_right | `pad_right(string, number, string)` | -| replace | `replace(string, string, string, number)` | -| reverse | `reverse(array\|string)` | -| sort | `sort(array[string]\|array[number])` | -| sort_by | `sort_by(array, expref)` | -| split | `split(string, string, number)` | -| starts_with | `starts_with(string, string)` | -| sum | `sum(array[number])` | -| to_array | `to_array(any)` | -| to_number | `to_number(any)` | -| to_string | `to_string(any)` | -| trim | `trim(string, string)` | -| trim_left | `trim_left(string, string)` | -| trim_right | `trim_right(string, string)` | -| type | `type(any)` | -| upper | `upper(string)` | -| values | `values(object)` | -| zip | `zip(array, array)` | - -## custom functions - -| Name | Signature | -|---|---| -| at | `at(array, any)` | -| concat | `concat(string, string)` | -| json_parse | `json_parse(string)` | -| wildcard | `wildcard(string, string)` | - -## kyverno functions - -| Name | Signature | -|---|---| -| compare | `compare(string, string)` | -| equal_fold | `equal_fold(string, string)` | -| replace | `replace(string, string, string, number)` | -| replace_all | `replace_all(string, string, string)` | -| to_upper | `to_upper(string)` | -| to_lower | `to_lower(string)` | -| trim | `trim(string, string)` | -| trim_prefix | `trim_prefix(string, string)` | -| split | `split(string, string)` | -| regex_replace_all | `regex_replace_all(string, string\|number, string\|number)` | -| regex_replace_all_literal | `regex_replace_all_literal(string, string\|number, string\|number)` | -| regex_match | `regex_match(string, string\|number)` | -| pattern_match | `pattern_match(string, string\|number)` | -| label_match | `label_match(object, object)` | -| to_boolean | `to_boolean(string)` | -| add | `add(any, any)` | -| sum | `sum(array)` | -| subtract | `subtract(any, any)` | -| multiply | `multiply(any, any)` | -| divide | `divide(any, any)` | -| modulo | `modulo(any, any)` | -| round | `round(number, number)` | -| base64_decode | `base64_decode(string)` | -| base64_encode | `base64_encode(string)` | -| time_since | `time_since(string, string, string)` | -| time_now | `time_now()` | -| time_now_utc | `time_now_utc()` | -| path_canonicalize | `path_canonicalize(string)` | -| truncate | `truncate(string, number)` | -| semver_compare | `semver_compare(string, string)` | -| parse_json | `parse_json(string)` | -| parse_yaml | `parse_yaml(string)` | -| lookup | `lookup(object\|array, string\|number)` | -| items | `items(object\|array, string, string)` | -| object_from_lists | `object_from_lists(array, array)` | -| random | `random(string)` | -| x509_decode | `x509_decode(string)` | -| time_to_cron | `time_to_cron(string)` | -| time_add | `time_add(string, string)` | -| time_parse | `time_parse(string, string)` | -| time_utc | `time_utc(string)` | -| time_diff | `time_diff(string, string)` | -| time_before | `time_before(string, string)` | -| time_after | `time_after(string, string)` | -| time_between | `time_between(string, string, string)` | -| time_truncate | `time_truncate(string, string)` | - diff --git a/website/docs/policies/asserts.md b/website/docs/policies/asserts.md deleted file mode 100644 index 72be0ee..0000000 --- a/website/docs/policies/asserts.md +++ /dev/null @@ -1,248 +0,0 @@ -# Assertion trees - -Assertion trees can be used to apply complex and dynamic conditional checks using [JMESPath](../jp.md) expressions. - -## Assert - -An `assert` declaration contains an `any` or `all` list in which each entry contains a: - -* `check`: the assertion check -* `message`: an optional message - -A check can contain one or more JMESPath expressions. Expressions represent projections of selected data in the JSON *payload* and the result of this projection is passed to descendants for further analysis. - -All comparisons happen in the leaves of the assertion tree. - -**A simple example**: - -This policy checks that a pod does not use the default service account: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: ValidatingPolicy -metadata: - name: assert-sample -spec: - rules: - - name: foo-bar - match: - all: - - apiVersion: v1 - kind: Pod - assert: - all: - - message: "serviceAccountName 'default' is not allowed" - check: - spec: - (serviceAccountName == 'default'): false -``` - -**A detailed example**: - -Given the input payload below: - -```yaml -foo: - baz: true - bar: 4 - bat: 6 -``` - -It is possible to write a validation rule like this: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: ValidatingPolicy -metadata: - name: test -spec: - rules: - - name: foo-bar-4 - validate: - assert: - all: - - message: "..." - check: - # project field `foo` onto itself, the content of `foo` becomes the current object for descendants - foo: - - # evaluate expression `(bar > `3`)`, the boolean result becomes the current object for descendants - # the `true` leaf is compared with the current value `true` - (bar > `3`): true - - # evaluate expression `(!baz)`, the boolean result becomes the current object for descendants - # the leaf `false` is compared with the current value `false` - (!baz): false - - # evaluate expression `(bar + bat)`, the numeric result becomes the current object for descendants - # the leaf `10` is compared with the current value `10` - (bar + bat): 10 -``` - -## Iterating with Projection Modifiers - -Assertion tree expressions support modifiers to influence the way projected values are processed. - -The `~` modifier applies to arrays and maps, it mean the input array or map elements will be processed individually by descendants. - -When the `~` modifier is not used, descendants receive the whole array, not each individual element. - -Consider the following input document: - -```yaml -foo: - bar: - - 1 - - 2 - - 3 -``` - -The policy below does not use the `~` modifier and `foo.bar` array is compared against the expected array: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: ValidatingPolicy -metadata: - name: test -spec: - rules: - - name: foo-bar - validate: - assert: - all: - - foo: - # the content of the `bar` field will be compared against `[1, 2, 3]` - bar: - - 1 - - 2 - - 3 -``` - -With the `~` modifier, we can apply descendant assertions to all elements in the array individually. -The policy below ensures that all elements in the input array are `< 5`: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: ValidatingPolicy -metadata: - name: test -spec: - rules: - - name: foo-bar - validate: - assert: - all: - - foo: - # with the `~` modifier all elements in the `[1, 2, 3]` array are processed individually and passed to descendants - ~.bar: - # the expression `(@ < `5`)` is evaluated for every element and the result is expected to be `true` - (@ < `5`): true -``` - -The `~` modifier supports binding the index of the element being processed to a named binding with the following syntax `~index_name.bar`. When this is used, we can access the element index in descendants with `$index_name`. - -When used with a map, the named binding receives the key of the element being processed. - -## Explicit bindings - -Sometimes it can be useful to refer to a parent node in the assertion tree. - -This is possible to add an explicit binding at every node in the tree by appending the `->binding_name` to the key. - -Given the input document: - -```yaml -foo: - bar: 4 - bat: 6 -``` - -The following policy will compute a sum and bind the result to the `sum` binding. A descendant can then use `$sum` and use it: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: ValidatingPolicy -metadata: - name: test -spec: - rules: - - name: foo-bar - validate: - assert: - all: - - foo: - # evaluate expression `(bar + bat)` and bind it to `sum` - (bar + bat)->sum: - # get the `$sum` binding and compare it against `10` - ($sum): 10 -``` - -All binding are available to descendants, if a descendant creates a binding with a name that already exists the binding will be overridden for descendants only and it doesn't affect the bindings at upper levels in the tree. - -In other words, a node in the tree always sees bindings that are defined in the parents and if a name is reused, the first binding with the given name wins when winding up the tree. - -As a consequence, the policy below will evaluate to true: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: ValidatingPolicy -metadata: - name: test -spec: - rules: - - name: foo-bar - validate: - assert: - all: - - foo: - (bar + bat)->sum: - ($sum + $sum)->sum: - ($sum): 20 - ($sum): 10 -``` - -Finally, we can always access the current payload, policy and rule being evaluated using the built-in `$payload`, `$policy` and `$rule` bindings. No protection is made to prevent you from overriding those bindings though. - - -## Escaping projection - -It can be necessary to prevent a projection under certain circumstances. - -Consider the following document: - -```yaml -foo: - (bar): 4 - (baz): - - 1 - - 2 - - 3 -``` - -Here the `(bar)` key conflict with the projection syntax. -To workaround this situation, you can escape a projection by surrounding it with `\` characters like this: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: ValidatingPolicy -metadata: - name: test -spec: - rules: - - name: foo-bar - validate: - assert: - all: - - foo: - \(bar)\: 10 -``` - -In this case, the leading and trailing `\` characters will be erased and the projection won't be applied. - -Note that it's still possible to use the `~` modifier or to create a named binding with and escaped projection. - -Keys like this are perfectly valid: - -- `~index.\baz\` -- `\baz\@foo` -- `~index.\baz\@foo` diff --git a/website/docs/pols/authorization-rules.md b/website/docs/policies/authorization-rules.md similarity index 100% rename from website/docs/pols/authorization-rules.md rename to website/docs/policies/authorization-rules.md diff --git a/website/docs/policies/authz-policy.md b/website/docs/policies/authz-policy.md deleted file mode 100644 index 5013244..0000000 --- a/website/docs/policies/authz-policy.md +++ /dev/null @@ -1,123 +0,0 @@ -# Policy Reference - -This page provides guidance on writing policies for request content processed by the kyverno-json validating policy, utilizing Envoy’s [External Authorization filter](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/security/ext_authz_filter.html). - -### Writing Policies - -Let start with an example policy that restricts access to an endpoint based on user's role and permissions. - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: ValidatingPolicy -metadata: - name: checkrequest -spec: - rules: - - name: deny-guest-request-at-post - assert: - any: - - message: "POST method calls at path /book are not allowed to guests users" - check: - request: - http: - method: POST - headers: - authorization: - (split(@, ' ')[1]): - (jwt_decode(@ , 'secret').payload.role): admin - path: /book - - message: "GET method call is allowed to both guest and admin users" - check: - request: - http: - method: GET - headers: - authorization: - (split(@, ' ')[1]): - (jwt_decode(@ , 'secret').payload.role): admin - path: /book - - message: "GET method call is allowed to both guest and admin users" - check: - request: - http: - method: GET - headers: - authorization: - (split(@, ' ')[1]): - (jwt_decode(@ , 'secret').payload.role): guest - path: /book -``` - -The above policy uses the `jwt_decode` builtin function to parse and verify the JWT containing information about the user making the request. it uses other builtins like `split`, `base64_decode`, `campare`, `contains` etc kyverno has many different [function](https://kyverno.github.io/kyverno-json/latest/jp/functions/) which can be used in policy. - -Sample input recevied by kyverno-json validating policy is shown below: - -```json -{ - "source": { - "address": { - "socketAddress": { - "address": "10.244.1.10", - "portValue": 59252 - } - } - }, - "destination": { - "address": { - "socketAddress": { - "address": "10.244.1.4", - "portValue": 8080 - } - } - }, - "request": { - "time": "2024-04-09T07:42:29.634453Z", - "http": { - "id": "14694995155993896575", - "method": "GET", - "headers": { - ":authority": "testapp.demo.svc.cluster.local:8080", - ":method": "GET", - ":path": "/book", - ":scheme": "http", - "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6Imd1ZXN0Iiwic3ViIjoiWVd4cFkyVT0ifQ.ja1bgvIt47393ba_WbSBm35NrUhdxM4mOVQN8iXz8lk", - "user-agent": "Wget", - "x-forwarded-proto": "http", - "x-request-id": "27cd2724-e0f4-4a69-a1b1-9a94edfa31bb" - }, - "path": "/book", - "host": "echo.demo.svc.cluster.local:8080", - "scheme": "http", - "protocol": "HTTP/1.1" - } - }, - "metadataContext": {}, - "routeMetadataContext": {} -} -``` - -With the help of assertion tree, we can write policies that can be used to validate the request content. - -An `assert` declaration contains an `any` or `all` list in which each entry contains a `check` and a `message`. The `check` contains a JMESPath expression that is evaluated against the request content. The `message` is a string that is returned when the check fails. -A check can contain one or more JMESPath expressions. Expressions represent projections of seleted data in the JSON payload and the result of this projection is passed to descendants for futher analysis. All comparisons happen in the leaves of the assertion tree. - -For more detail checkout [Policy Structure](https://kyverno.github.io/kyverno-json/latest/policies/policies/) and [Assertion trees](https://kyverno.github.io/kyverno-json/latest/policies/asserts/#assertion-trees). - -- HTTP method `request.http.method` -- Request path `request.http.path` -- Authorization header `request.http.headers.authorization` - -when we decode this above mentioned JWT token in the request payload we get payload.role `guest`: - -```json -{ - "exp": 2241081539, - "nbf": 1514851139, - "role": "guest", - "sub": "YWxpY2U=" -} -``` -With the input value above, the answer is: -``` -true -``` diff --git a/website/docs/pols/failure-policy.md b/website/docs/policies/failure-policy.md similarity index 100% rename from website/docs/pols/failure-policy.md rename to website/docs/policies/failure-policy.md diff --git a/website/docs/pols/index.md b/website/docs/policies/index.md similarity index 100% rename from website/docs/pols/index.md rename to website/docs/policies/index.md diff --git a/website/docs/policies/policies.md b/website/docs/policies/policies.md deleted file mode 100644 index 8eccf1e..0000000 --- a/website/docs/policies/policies.md +++ /dev/null @@ -1,100 +0,0 @@ -# Policy Structure - -Kyverno policies are [Kubernetes resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) and can be easily managed via Kubernetes APIs, GitOps workflows, and other existing tools. - -Policies that apply to JSON payload have a few differences from Kyverno policies that are applied to Kubernetes resources at admission controls. - -## Resource Scope - -Policies that apply to JSON payloads are always cluster-wide resources. - -## API Group and Kind - -`kyverno-json` policies belong to the `json.kyverno.io` group and can only be of kind `ValidatingPolicy`. - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: ValidatingPolicy -metadata: - name: test -spec: - rules: - - name: foo-bar-4 - validate: - assert: - all: - - foo: - bar: 4 -``` - -## Policy Rules - -A policy can have multiple rules, and rules are processed in order. Evaluation stops at the first rule that fails. - -## Match and Exclude - -Policies that apply to JSON payloads use [assertion trees](./asserts.md) in both the `match`/`exclude` declarations as well as the `validate` rule declaration. - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: ValidatingPolicy -metadata: - name: required-s3-tags -spec: - rules: - - name: require-team-tag - identifier: address - match: - any: - - type: aws_s3_bucket - exclude: - any: - - name: bypass-me - validate: - assert: - all: - - values: - tags: - Team: ?* -``` - -In the example above, every *resource* having `type: aws_s3_bucket` will match, and *payloads* having `name: bypass-me` will be excluded. - -## Identifying Payload Entries - -A policy rule can contain an optional `identifier` which declares the path to the payload element that uniquely identifies each entry. - -## Context Entries - -A policy rule can contain optional `context` entries that are made available to the rule via bindings: - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: ValidatingPolicy -metadata: - name: required-s3-tags -spec: - rules: - - name: require-team-tag - match: - any: - - type: aws_s3_bucket - context: - # creates a `expectedTeam` binding automatically - - name: expectedTeam - variable: Kyverno - validate: - message: Bucket `{{ name }}` does not have the required Team tag {{ $expectedTeam }} - assert: - all: - - values: - tags: - # use the `$expectedTeam` binding coming from the context - Team: ($expectedTeam) -``` - -## No `forEach`, `pattern operators`, `anchors`, or `wildcards` - -The use of [assertion trees](./asserts.md) addresses some features of Kyverno policies that apply to Kubernetes resources. - -Specifically, [forEach](https://kyverno.io/docs/writing-policies/validate/#foreach), [pattern operators](https://kyverno.io/docs/writing-policies/validate/#operators), [anchors](https://kyverno.io/docs/writing-policies/validate/#anchors), or [wildcards](https://kyverno.io/docs/writing-policies/validate/#wildcards) are not supported for policies that apply to JSON resources. Instead, [assertion trees](./asserts.md) with [JMESPath](../jp.md) expressions are used to achieve the same powerful features. \ No newline at end of file diff --git a/website/docs/pols/variables.md b/website/docs/policies/variables.md similarity index 100% rename from website/docs/pols/variables.md rename to website/docs/policies/variables.md diff --git a/website/docs/quick-start.md b/website/docs/quick-start.md deleted file mode 100644 index 2dc7092..0000000 --- a/website/docs/quick-start.md +++ /dev/null @@ -1,181 +0,0 @@ -# Quick Start - -This section presumes testing is conducted with Envoy version 1.10.0 or newer. - -### Required tools - -1. [`minikube`](https://minikube.sigs.k8s.io/docs/) -1. [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/) - -### Create a local cluster - -Start minikube cluster with the following command: - -```console -minikube start -``` -### Install kyverno-envoy sidecar with application - -Install application with envoy and kyverno-envoy-plugin as a sidecar container. - -```console -kubectl apply -f https://raw.githubusercontent.com/kyverno/kyverno-envoy-plugin/main/quick_start.yaml -``` -The `applicaition.yaml` manifest defines the following resource: - -- The Deployment includes an example Go application that provides information of books in the library books collection and exposes APIs to `get`, `create` and `delete` books collection. Check this out for more information about the [Go test application](https://github.com/Sanskarzz/kyverno-envoy-demos/tree/main/test-application) . - -- The Deployment also includes a kyverno-envoy-plugin sidecar container in addition to the Envoy sidecar container. When Envoy recevies API request destined for the Go test applicaiton, it will check with kyverno-envoy-plugin to decide if the request should be allowed and the kyverno-envoy-plugin sidecar container is configured to query Kyverno-json engine for policy decisions on incoming requests. - -- A ConfigMap `policy-config` is used to pass the policy to kyverno-envoy-plugin sidecar in the namespace `default` where the application is deployed . - -- A ConfigMap `envoy-config` is used to pass an Envoy configuration with an External Authorization Filter to direct authorization checks to the kyverno-envoy-plugin sidecar. - -- The Deployment also includes an init container that install iptables rules to redirect all container traffic to the Envoy proxy sidecar container , more about init container can be found [here](https://github.com/kyverno/kyverno-envoy-plugin/tree/main/demo/standalone-envoy/envoy_iptables) - -### Make Test application accessible in the cluster . - -```console -kubectl expose deployment testapp --type=NodePort --name=testapp-service --port=8080 -``` - -### Set the `SERVICE_URL` environment variable to the service's IP/port. - -minikube: - -```sh -export SERVICE_PORT=$(kubectl get service testapp -o jsonpath='{.spec.ports[?(@.port==8080)].nodePort}') -export SERVICE_HOST=$(minikube ip) -export SERVICE_URL=$SERVICE_HOST:$SERVICE_PORT -echo $SERVICE_URL -``` -### Calling the sample test application and verify the authorization - -For convenience, we’ll want to store Alice’s and Bob’s tokens in environment variables. Here bob is assigned the admin role and alice is assigned the guest role. - -```bash -export ALICE_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6Imd1ZXN0Iiwic3ViIjoiWVd4cFkyVT0ifQ.ja1bgvIt47393ba_WbSBm35NrUhdxM4mOVQN8iXz8lk" -export BOB_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6ImFkbWluIiwic3ViIjoiWVd4cFkyVT0ifQ.veMeVDYlulTdieeX-jxFZ_tCmqQ_K8rwx2OktUHv5Z0" -``` - -The policy we passed to kyverno-envoy-plugin sidecar in the ConfigMap `policy-config` is configured to check the conditions of the incoming request and denies the request if the user is a guest and the request method is `POST` at the `/book` path. - -```yaml -apiVersion: json.kyverno.io/v1alpha1 -kind: ValidatingPolicy -metadata: - name: checkrequest -spec: - rules: - - name: deny-guest-request-at-post - assert: - any: - - message: "POST method calls at path /book are not allowed to guests users" - check: - request: - http: - method: POST - headers: - authorization: - (split(@, ' ')[1]): - (jwt_decode(@ , 'secret').payload.role): admin - path: /book - - message: "GET method call is allowed to both guest and admin users" - check: - request: - http: - method: GET - headers: - authorization: - (split(@, ' ')[1]): - (jwt_decode(@ , 'secret').payload.role): admin - path: /book - - message: "GET method call is allowed to both guest and admin users" - check: - request: - http: - method: GET - headers: - authorization: - (split(@, ' ')[1]): - (jwt_decode(@ , 'secret').payload.role): guest - path: /book -``` - -Check for `Alice` which can get book but cannot create book. - -```bash -curl -i -H "Authorization: Bearer "$ALICE_TOKEN"" http://$SERVICE_URL/book -``` -```bash -curl -i -H "Authorization: Bearer "$ALICE_TOKEN"" -d '{"bookname":"Harry Potter", "author":"J.K. Rowling"}' -H "Content-Type: application/json" -X POST http://$SERVICE_URL/book -``` -Check the `Bob` which can get book also create the book - -```bash -curl -i -H "Authorization: Bearer "$BOB_TOKEN"" http://$SERVICE_URL/book -``` - -```bash -curl -i -H "Authorization: Bearer "$BOB_TOKEN"" -d '{"bookname":"Harry Potter", "author":"J.K. Rowling"}' -H "Content-Type: application/json" -X POST http://$SERVICE_URL/book -``` - -Check on logs -```bash -kubectl logs "$(kubectl get pod -l app=testapp -o jsonpath={.items..metadata.name})" -c kyverno-envoy-plugin -f -``` -First , third and last request is passed but second request is failed. - -```console -sanskar@sanskar-HP-Laptop-15s-du1xxx:~$ kubectl logs "$(kubectl get pod -l app=testapp -n demo -o jsonpath={.items..metadata.name})" -n demo -c kyverno-envoy-plugin -f -Starting HTTP server on Port 8000 -Starting GRPC server on Port 9000 -Request is initialized in kyvernojson engine . -2024/04/26 17:11:42 Request passed the deny-guest-request-at-post policy rule. -Request is initialized in kyvernojson engine . -2024/04/26 17:22:11 Request violation: -> POST method calls at path /book are not allowed to guests users - -> any[0].check.request.http.headers.authorization.(split(@, ' ')[1]).(jwt_decode(@ , 'secret').payload.role): Invalid value: "guest": Expected value: "admin" --> GET method call is allowed to both guest and admin users - -> any[1].check.request.http.headers.authorization.(split(@, ' ')[1]).(jwt_decode(@ , 'secret').payload.role): Invalid value: "guest": Expected value: "admin" - -> any[1].check.request.http.method: Invalid value: "POST": Expected value: "GET" --> GET method call is allowed to both guest and admin users - -> any[2].check.request.http.method: Invalid value: "POST": Expected value: "GET" -Request is initialized in kyvernojson engine . -2024/04/26 17:23:13 Request passed the deny-guest-request-at-post policy rule. -Request is initialized in kyvernojson engine . -2024/04/26 17:23:55 Request passed the deny-guest-request-at-post policy rule. -``` - -### Configuration - -To deploy Kyverno-Envoy include the following container in your kubernetes Deployments: - -```yaml -- name: kyverno-envoy-plugin - image: sanskardevops/plugin:0.0.34 - imagePullPolicy: IfNotPresent - ports: - - containerPort: 8181 - - containerPort: 9000 - volumeMounts: - - readOnly: true - args: - - "serve" - - "--policy=/policies/policy.yaml" - - "--address=:9000" - - "--healthaddress=:8181" - livenessProbe: - httpGet: - path: /health - scheme: HTTP - port: 8181 - initialDelaySeconds: 5 - periodSeconds: 5 - readinessProbe: - httpGet: - path: /health - scheme: HTTP - port: 8181 - initialDelaySeconds: 5 - periodSeconds: 5 -``` \ No newline at end of file diff --git a/website/docs/quick-start/install.md b/website/docs/quick-start/authz-server.md similarity index 92% rename from website/docs/quick-start/install.md rename to website/docs/quick-start/authz-server.md index 9ece33b..edc635c 100644 --- a/website/docs/quick-start/install.md +++ b/website/docs/quick-start/authz-server.md @@ -1,8 +1,10 @@ -# Installation +# Authz server -## Setup +In this quick start guide we will deploy the Kyverno Authz Server inside a cluster. + +Then you will interface [Istio](https://istio.io/latest/), an open source service mesh with the Kyverno Authz Server to delegate the request authorisation based on policies installed in the cluster. -In this quick start guide we will deploy the Kyverno Authz Server in a standalone mode inside the cluster. +## Setup ### Prerequisites @@ -186,3 +188,9 @@ The following request will return `200` (allowed by our policy): ```bash curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true" ``` + +## Wrap Up + +Congratulations on completing the quick start guide! + +This tutorial demonstrated how to configure Istio’s EnvoyFilter to utilize the Kyverno Authz Server as an external authorization service. diff --git a/website/docs/quick-start/next-steps.md b/website/docs/quick-start/next-steps.md index be67b89..d59eac6 100644 --- a/website/docs/quick-start/next-steps.md +++ b/website/docs/quick-start/next-steps.md @@ -1,3 +1,14 @@ # Next steps -TODO +We covered the main components of the Kyverno Envoy Plugin. + +!!!tip + + If there's anything you would like to be improved, please [reach out](https://github.com/kyverno/kyverno-envoy-plugin), we will be happy to discuss and improve as much as we can. + +To continue exploring and learn more about the Kyverno Envoy Plugin: + +- [Start writing your own policies](../policies/index.md) +- [Browse the tutorials section](../tutorials/index.md) +- [Consult the Reference documentation](../reference/index.md) +- [Engage with our Community and start contributing](../community/index.md) diff --git a/website/docs/quick-start/sidecar-injector.md b/website/docs/quick-start/sidecar-injector.md new file mode 100644 index 0000000..b7cd929 --- /dev/null +++ b/website/docs/quick-start/sidecar-injector.md @@ -0,0 +1,3 @@ +# Sidecar injector + +This is not ready yet, hopefully it will be available soon! \ No newline at end of file diff --git a/website/docs/tutorials/index.md b/website/docs/tutorials/index.md new file mode 100644 index 0000000..7e98189 --- /dev/null +++ b/website/docs/tutorials/index.md @@ -0,0 +1,6 @@ +# Tutorials + +If you didn't read the Quick start section yet, we really recommend giving it a try to discover and familiarise with the Kyverno Envoy Plugin components first. + +- [Authz server quick start](../quick-start/authz-server.md) +- [Sidecar injector quick start](../quick-start/sidecar-injector.md) \ No newline at end of file diff --git a/website/docs/tutorials/istio.md b/website/docs/tutorials/istio.md deleted file mode 100644 index b4688d6..0000000 --- a/website/docs/tutorials/istio.md +++ /dev/null @@ -1,343 +0,0 @@ -# Istio - -[Istio](https://istio.io/latest/) is an open source service mesh for managing the different microservices that make up a cloud-native application. Istio provides a mechanism to use a service as an external authorizer with the [AuthorizationPolicy API](https://istio.io/latest/docs/tasks/security/authorization/authz-custom/). - -This tutorial shows how Istio’s AuthorizationPolicy can be configured to delegate authorization decisions to Kyverno-envoy-plugin. - -## Prerequisites - -This tutorial requires Kubernetes 1.20 or later. To run the tutorial locally ensure you start a cluster with Kubernetes version 1.20+, we recommend using [minikube](https://kubernetes.io/docs/getting-started-guides/minikube) or [KIND](https://kind.sigs.k8s.io/). - -The tutorial also requries istio v1.19.0 or later. To install istio, follow the instructions [here](https://istio.io/latest/docs/setup/getting-started/) or run the below script it will create a kind cluster and install istio - -```sh -#!/bin/bash - -KIND_IMAGE=kindest/node:v1.29.2 -ISTIO_REPO=https://istio-release.storage.googleapis.com/charts -ISTIO_NS=istio-system - -# Create Kind cluster -kind create cluster --image $KIND_IMAGE --wait 1m --config - < 8080/TCP 4h5m - -NAME READY UP-TO-DATE AVAILABLE AGE -deployment.apps/echo 1/1 1 1 3h59m - -NAME DESIRED CURRENT READY AGE -replicaset.apps/echo-55c77757f4 1 1 1 3h59m -``` - -### ServiceEntry - -ServiceEntry to registor the kyverno-envoy-plugin sidecar as external authorizer and ServiceEntry to allow Istio to find the Kyverno-Envoy-Plugin sidecar. - -```console -kubectl apply -f ./manifests/service-entry.yaml -``` -```yaml -apiVersion: networking.istio.io/v1beta1 -kind: ServiceEntry -metadata: - name: kyverno-ext-authz-grpc-local -spec: - hosts: - - "kyverno-ext-authz-grpc.local" - # The service name to be used in the extension provider in the mesh config. - endpoints: - - address: "127.0.0.1" - ports: - - name: grpc - number: 9000 - # The port number to be used in the extension provider in the mesh config. - protocol: GRPC - resolution: STATIC -``` -### Register authorization provider - -Edit the mesh configmap to register authorization provider with the following command: - -```console -kubectl edit configmap istio -n istio-system -``` - -In the editor, add the extension provider definitions to the mesh configmap. - -```yaml - data: - mesh: |- - extensionProviders: - - name: "kyverno-ext-authz-grpc" - envoyExtAuthzGrpc: - service: "kyverno-ext-authz-grpc.local" - port: "9000" -``` - -### Authorization policy - -AuthorizationPolicy to direct authorization checks to the Kyverno-Envoy-Plugin sidecar. - -```shell -$ kubectl apply -f - < + # size(variables.authorization) == 2 && variables.authorization[0] == "bearer" + # ? jwt.Decode(variables.authorization[1], "secret") + # : null + authorizations: + - expression: > + size(variables.authorization) == 2 && variables.authorization[0] == "Bearer" + ? envoy.Allowed().Response() + : envoy.Denied(403).Response() +EOF + + +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: checkrequest +spec: + rules: + - name: deny-guest-request-at-post + assert: + any: + - message: "POST method calls at path /book are not allowed to guests users" + check: + request: + http: + method: POST + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): admin + path: /book + - message: "GET method call is allowed to both guest and admin users" + check: + request: + http: + method: GET + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): admin + path: /book + - message: "GET method call is allowed to both guest and admin users" + check: + request: + http: + method: GET + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): guest + path: /book +``` + +## Testing + +At this we have deployed and configured Istio, the Kyverno Authz Server, a sample application, and the authorization policies. + +### Start an in-cluster shell + +Let's start a pod in the cluster with a shell to call into the sample application. + +```bash +# run an in-cluster shell +kubectl run -i -t busybox --image=alpine --restart=Never -n demo +``` + +### Install curl + +We will use curl to call into the sample application but it's not installed in our shell, let's install it in the pod. + +```bash +# install curl +apk add curl +``` + +### Call into the sample application + +Now we can send request to the sample application and verify the result. + +For convenience, we’ll want to store Alice’s and Bob’s tokens in environment variables. + +Here Bob is assigned the admin role and Alice is assigned the guest role. + +```bash +export ALICE_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6Imd1ZXN0Iiwic3ViIjoiWVd4cFkyVT0ifQ.ja1bgvIt47393ba_WbSBm35NrUhdxM4mOVQN8iXz8lk" +export BOB_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6ImFkbWluIiwic3ViIjoiWVd4cFkyVT0ifQ.veMeVDYlulTdieeX-jxFZ_tCmqQ_K8rwx2OktUHv5Z0" +``` + +Calling without a JWT token will return `401`: + +```bash +curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get +``` + +Calling with Alice’s JWT token will return `403`: + +```bash +curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "authorization: Bearer $ALICE_TOKEN" +``` + +Calling with Bob’s JWT token will return `200`: + +```bash +curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "authorization: Bearer $BOB_TOKEN" +``` + +## Wrap Up + +Congratulations on completing the tutorial! + +This tutorial demonstrated how to configure Istio’s EnvoyFilter to utilize the Kyverno Authz Server as an external authorization service. + +Additionally, the tutorial provided an example policy to decode a JWT token and make a decision based on it. diff --git a/website/mkdocs.yaml b/website/mkdocs.yaml index 8309085..00d0945 100644 --- a/website/mkdocs.yaml +++ b/website/mkdocs.yaml @@ -4,31 +4,24 @@ nav: - Home: index.md - Quick start: - quick-start/index.md - - quick-start/install.md + - quick-start/authz-server.md + - quick-start/sidecar-injector.md - quick-start/next-steps.md - Policies: - - pols/index.md - - pols/failure-policy.md - - pols/variables.md - - pols/authorization-rules.md + - policies/index.md + - policies/failure-policy.md + - policies/variables.md + - policies/authorization-rules.md - CEL extensions: - cel-extensions/index.md - cel-extensions/envoy.md - cel-extensions/jwt.md - Performance: - performance/index.md -- Documentation: - - quick-start.md - - Writing policies: - - policies/policies.md - - policies/asserts.md - - policies/authz-policy.md - - JMESPath: - - Overview: jp.md - - Functions: jp/functions.md - Tutorials: + - tutorials/index.md + - tutorials/istio/index.md - tutorials/standalone-envoy.md - - tutorials/istio.md - tutorials/mtls-istio.md - Reference: - reference/index.md