diff --git a/DocsArchitecture.md b/DocsArchitecture.md new file mode 100644 index 00000000..94324fbe --- /dev/null +++ b/DocsArchitecture.md @@ -0,0 +1,533 @@ +# Overview + +This document provides a high-level overview of the architecture and design for the Kyverno Envoy plugin. + +## Architecture + +Building the authorization plugin requires three components: + +- A sample upstream service: This service simulates real-world applications that the plugin will authorize it. +- An Envoy proxy service: This service acts as a mediator, routing traffic and enforcing authorization decisions made by the Kyverno authorization server. +- The Kyverno authorization service: This is the core of the plugin, responsible for making authorization decisions based on Kyverno policies. + +Basically the Architecture of the plugin will be different in different scenario which means when cluster already uses application like Istio or gloo-edge where envoy is already deployed in the cluster in that case we will integrate our kyverno authz server with existing envoy proxy setup. + +In this explanation, we'll assume there are no other Envoy proxy solutions like Istio or Gloo Edge already deployed in the cluster. I've explained how to achieve authorization in a service mesh with Kyverno as the authorization server below. + +![Architecture](demo/istio/architecture1.png) + +In this design the application/service pod will include envoy sidecar and kyverno authz server sidecar + +### init container + +To redirect all container traffic throught the Envoy proxy sidecare, In this init container the [Istio Proxy init script](https://github.com/open-policy-agent/contrib/tree/main/envoy_iptables) will be executed . + +### Envoy sidecar container + +Envoy (v1.7.0+) supports an External Authorization filter which calls an authorization service to check if the incoming request is authorized or not . [External Authorization filter](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/security/ext_authz_filter.html) feature will help us to make a decision based on Kyverno policies . + +This sidecar will include Envoy cofiguration to configure kyverno authz server to make decision on the incoming request + +```yml +admin: + access_log_path: /dev/stdout + address: + socket_address: { address: 0.0.0.0, port_value: 9901 } + +static_resources: + listeners: + - name: listener1 + address: + socket_address: { address: 0.0.0.0, port_value: 51051 } + 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 + stat_prefix: testsrv + codec_type: AUTO + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: { prefix: "/" } + route: { cluster: upstream } + http_filters: + - name: envoy.ext_authz + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz + transport_api_version: V3 + failure_mode_allow: false + grpc_service: + envoy_grpc: + cluster_name: kyverno-ext-authz + with_request_body: + allow_partial_message: true + max_request_bytes: 1024 + pack_as_bytes: true + - name: envoy.filters.http.router + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + + clusters: + - name: upstream-service + type: STRICT_DNS + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: upstream-service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 + + - name: kyverno-ext-authz + connect_timeout: 1.25s + type: LOGICAL_DNS + lb_policy: ROUND_ROBIN + http2_protocol_options: {} + load_assignment: + cluster_name: kyverno-ext-authz + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: kyverno-ext-authz + port_value: 9000 +``` + +The external authorization filter calls an external gRPC service to check whether an incoming HTTP request is authorized or not. If the request is deemed unauthorized, then the request will be denied normally with 403 (Forbidden) response. + +The content of the requests that are passed to kyverno ext authz service is specified by [CheckRequest](https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkrequest) and Envoy receives responce as [CheckResponce](https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkresponse). + +### Kyverno External Authz server + +The plugin server extends kyverno-JSON with a GRPC server that implements the Envoy [External Authorization API](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter#config-http-filters-ext-authz) + +This Kyverno authz server will accept Arguments like multiple kyverno-json validation policy file or path to policy file , address and path of the grpc server as mentioned in envoy config + +This authz server will recive [service.auth.v3.CheckRequest](https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkrequest) JSON payload + +The request info will look like this and all authentication info send by user also be present here in this payload only. + +```json +{ + "source": {...}, + "destination": {...}, + "request": {...}, + "context_extensions": {...}, + "metadata_context": {...}, + "route_metadata_context": {...}, + "tls_session": {...} +} +``` + +This CheckRequest payload has all information like request information , authentication information , path , method etc + +For example +```json +{ + "source": { + "address": { + "socket_address": { + "address": "10.244.0.10", + "port_value": 43466 + } + } + }, + "destination": { + "address": { + "socket_address": { + "address": "10.244.0.7", + "port_value": 8080 + } + } + }, + "request": { + "time": { + "seconds": 1710826031, + "nanos": 497170000 + }, + "http": { + "id": "5368491147255081293", + "method": "GET", + "headers": { + ":authority": "echo.demo.svc.cluster.local:8080", + ":method": "GET", + ":path": "/foo", + ":scheme": "http", + "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiZ3Vlc3QiLCJzdWIiOiJZV3hwWTJVPSIsIm5iZiI6MTUxNDg1MTEzOSwiZXhwIjoxOTQxMDgxNTM5fQ.rN_hxMsoQzCjg6lav6mfzDlovKM9azaAjuwhjq3n9r8", + "user-agent": "Wget", + "x-forwarded-proto": "http", + "x-request-id": "ee4c2a8e-1c4b-46b9-b54a-df5e042fe652" + }, + "path": "/foo", + "host": "echo.demo.svc.cluster.local:8080", + "scheme": "http", + "protocol": "HTTP/1.1" + } + }, + "metadata_context": {}, + "route_metadata_context": {} +} +``` + +For evaluation we will use Kyverno-Json Go Api which is a way to embed the kyverno-JSON engine in Go program that validate JSON payloads using Kyverno policies. +This CheckRequest json payload will executed/scan against accepted Argument validation policy.yaml in the kyverno-json engine and response of the engine will be converted into [CheckResponse](https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkresponse) and CheckResponse return back to envoy if request is deemed unauthorized then request will be denied normally with 403(Forbidden) reponse if the request is authorized then the request will be accepted with status OK responce then envoy will redirect request to upstream service . + + +### Kyverno Policy + + +How we will access and restrict request information in the policy we will use builtin and kyverno function to make it work . +To decode the session token and jwt token the following function will be used for finding users and roles . + +Function like +- split(string, string) + To split the strings like to split authorization token "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiZ3Vlc3QiLCJzdWIiOiJZV3hwWTJVPSIsIm5iZiI6MTUxNDg1MTEzOSwiZXhwIjoxOTQxMDgxNTM5fQ.rN_hxMsoQzCjg6lav6mfzDlovKM9azaAjuwhjq3n9r8" + + + split(request.http.authorization, " ") it will split the Bearer and token , space is used as a delimiter + +- base64_decode(string) + To decode the encoded session based authentication tokens to produce user role + +- We need to support the jwt verification functions to decode and verify signature jwt token like opa has built in [Token verification functions](https://www.openpolicyagent.org/docs/latest/policy-reference/#tokens) + + + +For example + +Here the user with there role + +`guest` role and can perform a `GET` request . +`admin` role and can perform a `GET` and `POST` request. + +The users will request to service with jwt token within the headers specifying their role + +```json +{ + "attributes": { + "request": { + "http": { + "method": "GET", + "headers": { + "authorization": "Bearer eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJleHAiOiAyMjQxMDgxNTM5LCAibmJmIjogMTUxNDg1MTEzOSwgInJvbGUiOiAiZ3Vlc3QiLCAic3ViIjogIllXeHBZMlU9In0.Uk5hgUqMuUfDLvBLnlXMD0-X53aM_Hlziqg3vhOsCc8" + } + } + } + } +} + +``` + +The policy should have features like spliting and decoding jwt token or session token + +```yml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: authz-policy +spec: + rules: + - assert: + any: + - check: + (attributes.request.http.headers.authorization) | split(@, ' ')[1] | jwtdecode(@,secret)[2].role == 'guest' + && (attributes.request.http.method) != 'GET' + - check: + (attributes.request.http.headers.authorization) | split(@, ' ')[1] | jwtdecode(@,secret)[2].role == 'admin' + && (attributes.request.http.method) != 'POST' + - check: + (attributes.request.http.headers.authorization) | split(@, ' ')[1] | jwtdecode(@,secret)[2].role == 'admin' + && (attributes.request.http.method) != 'GET' + +``` + +When we decode the jwt token the payload data is + +```json +{ + "exp": 2241081539, + "nbf": 1514851139, + "role": "guest", + "sub": "YWxpY2U=" +} + +``` + +To make it more user friendly experiance we need more builtin and custom function for matching and decoding which should be easy to use . + +The policy evaluation result, either 'pass' or 'fail', will be embed into the [CheckResponse](https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkresponse) message and sent back to Envoy. If policy result is `pass` then CheckResponse status is set to be `OK` or `200` then the incoming request will be allowed and if policy result `fail` then CheckResponse status is set to be `403` then the incoming request will be denied . + + +### Deployemnt of Upstream sevice with the sidecar containers + +Upstream App deployment with kyverno-envoy and Envoy as a sidecar + +Example The deployment be like + +```yml +apiversion: apps/v1 +kind: Deployment +metadata: + name: example-app + labels: + app: example-app +spec: + replicas: 1 + selector: + matchLabels: + app: example-app + template: + metadata: + labels: + app: example-app + spec: + initContainers: + - name: proxy-init + image: istio/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 8282 from the proxy for the + # kyverno health checks. These values must match up with the configuration + # defined below for the "envoy" and "kyvermo" containers. + args: ["-p", "8000", "-u", "1111", "-w", "8282"] + securityContext: + capabilities: + add: + - NET_ADMIN + runAsNonRoot: false + runAsUser: 0 + containers: + - name: app + image: sanskardevops/testservice:latest # Upstream service + ports: + - containerPort: 8080 + - name: envoy + image: envoyproxy/envoy:v1.20.0 + env: + - name: ENVOY_UID + value: "1111" + volumeMounts: + - readOnly: true + mountPath: /config + name: proxy-config + - readOnly: false + mountPath: /run/sockets + name: emptyDir + args: + - "envoy" + - "--log-level" + - "debug" + - "--config-path" + - "/config/envoy.yaml" + - name: kyverno-envoy + image: sanskardevops/kyverno-envoy:0.0.1 #authorization service + securityContext: + runAsUser: 1111 + volumeMounts: + - readOnly: true + mountPath: /policy + name: kyverno-policy + - readOnly: false + mountPath: /run/sockets + name: emptyDir + containerPort: 9002 + args: + - "serve" + - "--policy=/policy/kyverno-policy.yaml" + - "--address=localhost:9002" + volumes: + - name: proxy-config + configMap: + name: proxy-config + - name: kyverno-policy + configMap: + name: kyverno-policy + - name: emptyDir + emptyDir: {} +``` + +The configuration such as policy.yaml, and Envoy configuration will be provided through volume mounted `ConfigMaps` within the deployment. + +```yml +apiVersion: v1 +kind: ConfigMap +metadata: + name: kyverno-policy +data: + policy.rego: | + apiVersion: json.kyverno.io/v1alpha1 + kind: ValidatingPolicy + metadata: + name: check-external-auth + spec: + rules: + - name: + assert: + all: + - message: "GET calls are not allowed" + check: + (request.http.method == 'GET'): false +``` + +## Kyverno Authorization Server with Istio Service Mesh + +Istio is an open source service mesh for manageing 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/) . + +The Istio service mesh already uses the envoy proxy so we will integrate our kyverno authorization server with istio envoy proxy , for this external authorization server implementation istio provides feature in the Istio authorization policy using action field value set to be `CUSTOM` to delegate the access control to an external authorization system which will be our kyverno authorization server + +#### Deployment of external authorizaion server + +Istio provides three type of deployment of external authorization server +- Deploy External authorizer in a standalone pod in the mesh +- Deploy External authorizer outside of the mesh +- Deploy External authorizer as a separate container as a sidecar container in the same pod of the application which needs authorization + +For last two deployment of external authorizer we need to create a service entry resource to register the service to the mesh and make sure it is accessible to the proxy + +Here are the pros and cons of each Deployment types + +- Standalone Pod in the Mesh: + Example of Deployment standalone Pod is show [Istio Demo](./demo/istio/README.md) + - Pros: + - Simpler deployment within the service and no need to register the service by service entry resource . + - Easier to manage alongside istio components + - Cons: + - Adds another service to the mesh, increasing complexity. + - Failure of the authorizer can impact overall mesh functionality. + - Scalability might be limited compared to external deployment. + +- External Deployment (Outside Mesh): + - Pros: + - Isolates authorization server from istio, improving resilience. + - Easier scaling of the authoricer independently. + - Cons: + - Increased network communication overhead for authorization checks. + - Latency will be higher compared to deployemnt with sidecare container. + - Potential management overhead for a separate service. + +- Sidecar Container in the same Pod: + - Pros: + - Tight integration with the application needing authorization. + - Minimizes network overhead for authorization checks and fastest on authorization checks as compared to others options. + - Efficient resource utilization by sharing a pod. + - Cons: + - Very complex installation of the sidecar . + - Failure of the application can impact authorization and vice versa. + + +Explaining Deployement of kyverno external authorization as sidecar container in same Pod + +- Consider tight coupling if the application and authorization logic are highly dependent and Considering minimum network overhead or lowest latency for authorization checks . If we automate or improve the installation then this method of deployment of external authorization will be best Way to deploy kyverno-envoy server as separate container in same pod or as sidecar container . + +![Architecture](demo/istio/architecture2.png) + +To automate or improve the installation + - we can add a Mutate webhook admission controller to add/inject our sidecar container with the pod , if the pod configuration has annotation like `kyverno-envoy-injection=enabled` then the admission controller automatically inject the kyverno-envoy sidecar container into pods and opa also uses this type of admission controller which injects the sidecar + + - To build the mutating webhook admission controller for injecting sidecar container we can take reference from an open-source project [tumblr/k8s-sidecar-injector](https://github.com/tumblr/k8s-sidecar-injector) + + - This Configuration will be injected by admission controller + ```yml + apiVersion: v1 + kind: ConfigMap + metadata: + name: test-injectionconfig1 + namespace: default + labels + app: k8s-sidecar-injector + data: + sidecar-v1: | + name: kyverno-envoy + containers: + - name: kyverno-envoy + image: sanskardevops/kyverno-envoy:0.0.1 #authorization service + securityContext: + runAsUser: 1111 + volumeMounts: + - readOnly: true + mountPath: /policy + name: kyverno-policy + containerPort: 9002 + args: + - "serve" + - "--policy=/policy/kyverno-policy.yaml" + - "--address=localhost:9002" + volumes: + - name: kyverno-policy + configMap: + name: kyverno-policy + ``` + +we need to define external authorizer that is allowed to be used in the mesh , so we need to define extension provider in the mesh config + +``` +kubectl edit configmap istio -n istio-system +``` +The following content will register external provider as kyverno external authorization server + +```yml +data: + mesh: |- + # Add the following content to define the external authorizers. + extensionProviders: + - name: "kyverno-ext-authz-grpc" + envoyExtAuthzGrpc: + service: "ext-authz.foo.svc.cluster.local" + port: "9000" + - name: "kyverno-ext-authz-http" + envoyExtAuthzHttp: + service: "ext-authz.foo.svc.cluster.local" + port: "8000" +``` +And we are deploying external authorizer as a separate container in the same pod of the application so we also need to create a service entry resource to register the service to the mesh and make it accessible to the proxy + +```yml +#Define the service entry for the local ext-authz service on port 9000. +apiVersion: networking.istio.io/v1alpha3 +kind: ServiceEntry +metadata: + name: kyverno-ext-authz-grpc +spec: + hosts: + - "ext-authz-grpc.local" + endpoints: + - address: "127.0.0.1" + ports: + - name: grpc + number: 9000 + protocol: GRPC + resolution: STATIC +``` +Then we have to apply authorization policy with the `CUSTOM` action value . +```yml + +apiVersion: security.istio.io/v1 +kind: AuthorizationPolicy +metadata: + name: ext-authz + namespace: demo +spec: + action: CUSTOM + provider: + # The provider name must match the extension provider defined in the mesh config. + # You can also replace this with sample-ext-authz-http to test the other external authorizer definition. + name: kyverno-ext-authz-grpc + rules: + # The rules specify when to trigger the external authorizer. + - to: + - operation: + paths: ["/headers"] + +``` + + + + diff --git a/demo/istio/manifests/ext-authz.yaml b/demo/istio/manifests/ext-authz.yaml new file mode 100644 index 00000000..0a6a512e --- /dev/null +++ b/demo/istio/manifests/ext-authz.yaml @@ -0,0 +1,40 @@ +apiVersion: v1 +kind: Service +metadata: + name: ext-authz + labels: + app: ext-authz + namespace: demo +spec: + ports: + - name: http + port: 8000 + targetPort: 8000 + - name: grpc + port: 9000 + targetPort: 9000 + selector: + app: ext-authz +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ext-authz + namespace: demo +spec: + replicas: 1 + selector: + matchLabels: + app: ext-authz + template: + metadata: + labels: + app: ext-authz + spec: + containers: + - image: ko.local/github.com/kyverno/kyverno-envoy-plugin:7bd39c9d958eb408a86cee2d97241895522b317f + imagePullPolicy: IfNotPresent + name: ext-authz + ports: + - containerPort: 8000 + - containerPort: 9000 \ No newline at end of file diff --git a/go.mod b/go.mod index b957a33a..6d7d7e9e 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,36 @@ module github.com/kyverno/kyverno-envoy-plugin go 1.21.4 require ( + github.com/envoyproxy/go-control-plane v0.12.0 + github.com/jmespath-community/go-jmespath v1.1.2-0.20240117150817-e430401a2172 + github.com/kyverno/kyverno-json v0.0.3-alpha.2 + google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe google.golang.org/grpc v1.62.1 k8s.io/apimachinery v0.29.2 ) require ( - github.com/go-logr/logr v1.3.0 // indirect + github.com/IGLOU-EU/go-wildcard v1.0.3 // indirect + github.com/aquilax/truncate v1.0.0 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa // indirect + github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect + github.com/jtolds/gls v4.20.0+incompatible // indirect + github.com/kr/text v0.2.0 // indirect + github.com/kyverno/kyverno v1.5.0-rc1.0.20240202083228-5f0d53fe3482 // indirect + github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect google.golang.org/protobuf v1.32.0 // indirect - k8s.io/klog/v2 v2.110.1 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 61809ef9..fab1141f 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,124 @@ -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/IGLOU-EU/go-wildcard v1.0.3 h1:r8T46+8/9V1STciXJomTWRpPEv4nGJATDbJkdU0Nou0= +github.com/IGLOU-EU/go-wildcard v1.0.3/go.mod h1:/qeV4QLmydCbwH0UMQJmXDryrFKJknWi/jjO8IiuQfY= +github.com/aquilax/truncate v1.0.0 h1:UgIGS8U/aZ4JyOJ2h3xcF5cSQ06+gGBnjxH2RUHJe0U= +github.com/aquilax/truncate v1.0.0/go.mod h1:BeMESIDMlvlS3bmg4BVvBbbZUNwWtS8uzYPAKXwwhLw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI= +github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gxui v0.0.0-20151028112939-f85e0a97b3a4 h1:OL2d27ueTKnlQJoqLW2fc9pWYulFnJYLWzomGV7HqZo= +github.com/google/gxui v0.0.0-20151028112939-f85e0a97b3a4/go.mod h1:Pw1H1OjSNHiqeuxAduB1BKYXIwFtsyrY47nEqSgEiCM= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/jmespath-community/go-jmespath v1.1.2-0.20240117150817-e430401a2172 h1:XQYEhx+bEiWn6eiHFivu4wEHm91FoZ/gCvoLZK6Ze5Y= +github.com/jmespath-community/go-jmespath v1.1.2-0.20240117150817-e430401a2172/go.mod h1:j4OeykGPBbhX3rw4AOPGXSmX2/zuWXktm704A4MtHFs= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kyverno/kyverno v1.5.0-rc1.0.20240202083228-5f0d53fe3482 h1:mGQbOhxoHBhPCGkxYjy+u3qtWH43BLjIxUs4zWtvKSg= +github.com/kyverno/kyverno v1.5.0-rc1.0.20240202083228-5f0d53fe3482/go.mod h1:uEm7WtaqOsPP3Jx6EOkO2PjHu6vf0MFaMD6w1ol7hAQ= +github.com/kyverno/kyverno-json v0.0.3-alpha.2 h1:uB/Nbwa1lz81wK+6RXD/0UjBJOwv9/RkhTPl4BlZRbI= +github.com/kyverno/kyverno-json v0.0.3-alpha.2/go.mod h1:EmCRYvDrIbMSmFouwYTdDee37xpWsr4vWhiJsxBDXSc= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/smarty/assertions v1.15.1 h1:812oFiXI+G55vxsFf+8bIZ1ux30qtkdqzKbEFwyX3Tk= +github.com/smarty/assertions v1.15.1/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea h1:CyhwejzVGvZ3Q2PSbQ4NRRYn+ZWv5eS1vlaEusT+bAI= +github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea/go.mod h1:eNr558nEUjP8acGw8FFjTeWvSgU1stO7FAO6eknhHe4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= -k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= +k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/main.go b/main.go index af363f88..65558a41 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "io" "log" "net" "net/http" @@ -11,21 +12,30 @@ import ( "syscall" "time" + authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "k8s.io/apimachinery/pkg/util/wait" ) type Servers struct { httpServer *http.Server grpcServer *grpc.Server + grpcV3 *extAuthzServerV3 } +type ( + extAuthzServerV3 struct{} +) + func NewServers() *Servers { - return &Servers{} + return &Servers{ + grpcV3: &extAuthzServerV3{}, + } } func (s *Servers) startHTTPServer(ctx context.Context) { - s.httpServer = &http.Server{ Addr: ":8000", Handler: http.HandlerFunc(handler), @@ -48,17 +58,41 @@ func (s *Servers) startHTTPServer(ctx context.Context) { } func handler(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "Hello World!") + fmt.Printf("Received request from %s %s\n", r.RemoteAddr, r.URL.Path) + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "Error reading request body", http.StatusInternalServerError) + return + } + defer r.Body.Close() + fmt.Println("Request payload:", string(body)) } -func (s *Servers) startGRPCServer(ctx context.Context) { +func (s *extAuthzServerV3) Check(ctx context.Context, req *authv3.CheckRequest) (*authv3.CheckResponse, error) { + attrs := req.GetAttributes() + + // Print each attribute individually + for key, value := range attrs.GetRequest().GetHttp().GetHeaders() { + fmt.Printf("Header: %s = %s\n", key, value) + } + // Print the entire struct with field names + fmt.Printf("Attributes: %+v\n", attrs) + // Implement your authorization logic here + // For now, allow all requests + return &authv3.CheckResponse{ + Status: &status.Status{Code: int32(codes.OK)}, + }, nil +} + +func (s *Servers) startGRPCServer(ctx context.Context) { lis, err := net.Listen("tcp", ":9000") if err != nil { log.Fatalf("failed to listen: %v", err) } s.grpcServer = grpc.NewServer() fmt.Println("Starting GRPC server on Port 9000") + authv3.RegisterAuthorizationServer(s.grpcServer, s.grpcV3) go func() { <-ctx.Done() diff --git a/pkg/scratch/scratch.go b/pkg/scratch/scratch.go new file mode 100644 index 00000000..dd51fb19 --- /dev/null +++ b/pkg/scratch/scratch.go @@ -0,0 +1,31 @@ +package scratch + +import ( + "context" + + jpfunctions "github.com/jmespath-community/go-jmespath/pkg/functions" + "github.com/jmespath-community/go-jmespath/pkg/interpreter" + "github.com/jmespath-community/go-jmespath/pkg/parsing" + "github.com/kyverno/kyverno-json/pkg/engine/template" +) + +var Caller = func() interpreter.FunctionCaller { + var funcs []jpfunctions.FunctionEntry + funcs = append(funcs, template.GetFunctions(context.Background())...) + return interpreter.NewFunctionCaller(funcs...) +}() + +func GetUser(authorisation string) (string, error) { + vm := interpreter.NewInterpreter(nil, nil) + parser := parsing.NewParser() + statement := `base64_decode(@)` + compiled, err := parser.Parse(statement) + if err != nil { + return "", err + } + out, err := vm.Execute(compiled, authorisation, interpreter.WithFunctionCaller(Caller)) + if err != nil { + return "", err + } + return out.(string), nil +} diff --git a/pkg/scratch/scratch_test.go b/pkg/scratch/scratch_test.go new file mode 100644 index 00000000..6e3c32d7 --- /dev/null +++ b/pkg/scratch/scratch_test.go @@ -0,0 +1,30 @@ +package scratch + +import "testing" + +func TestGetUser(t *testing.T) { + tests := []struct { + name string + authorisation string + want string + wantErr bool + }{ + { + authorisation: "YWxpY2U6cGFzc3dvcmQ=", + want: "alice:password", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetUser(tt.authorisation) + if (err != nil) != tt.wantErr { + t.Errorf("GetUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("GetUser() = %v, want %v", got, tt.want) + } + }) + } +}