Envoy LUA filter, contact local container for external authorization

Hi all,
we’ve deployed a sidecar for authorization purposes, that will contact our authorization service. Basically we’ll have our pod with 3 containers:

  • Microservice
  • Authorization sidecar
  • Istio proxy

We were planning to deploy an Envoy LUA filter in front of our pod to intercept each HTTP request and forward it to our authorization service, which in turn will perform the authorization checks. How can i reach that container? I mean: inside the LUA authorization filter we’ve to use the following function to perform http call: request_handle:httpCall. How can i reach, with this function, an internal container belonging to the same pod? I’ve tried with localhost (due to the fact that containers inside the same pod share the same network stack) but it doesn’t work. Any ideas on that?

Thank you very much,
Matteo

httpCall takes an Envoy “cluster” name as its argument, not a hostname like localhost.

What you need to do is create a ServiceEntry that represents your authz service, then find out the cluster name that Istio assigns to it, and use that name.

Just curious, if you need external authorization, why not just use ext_authz filter?

Yeah, yesterday we found a pr on github which was telling about service entries + envoy config and at the end we created a specific service entry and it works :wink:

@YangminZhu we’ve used LUA filter because we needed to pass specific custom headers to the underlying authorization sidecar (like specific path and method of the incoming request that needed to be authorized against our authorization server) and the LUA filter gives us more flexibility. Due how it’s implemented our authorization sidecar this was the easiest and fastest solution. We’re going to do some performance tests on that and we’ll see if we’ll need to change our implementation to ext_authz filter.
Anyway, the problem about how to contact an internal container was still present with the ext_authz filter :wink:

Thank you!

Thanks for the information.

I’m very interested in the performance test result, would you mind to share the result when you got the data?

I have done micro benchmark between Lua filter and RBAC filter by enforcing the same sample access control policy. The result is Lua filter is around 5x slower than the RBAC filter. When comparing with external authorization, I guess the latency will mainly be dominated by the external request.

Can you share the absolute numbers (e.g. number of microseconds) in addition to the relative?

Was the Lua filter in your test implementing the policy, or was it sending an request to an external service that was implementing the policy?

Hi Spike, sorry for the late reply I forget to follow on this, I tested the Lua implementing the policy directly. I didn’t test any external requests. The following table shows the raw benchmark data, together with some other implementations (CEL and C++):

2019-08-06 21:25:05
Running ./bazel-bin/test/cel/benchmark_test
Run on (12 X 4000 MHz CPU s)
CPU Caches:
  L1 Data 32K (x6)
  L1 Instruction 32K (x6)
  L2 Unified 256K (x6)
  L3 Unified 15360K (x1)
Load Average: 1.08, 1.47, 1.79
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.
-----------------------------------------------------------
Benchmark                 Time             CPU   Iterations
-----------------------------------------------------------
Native                 108 ns          108 ns     5957712
RBAC                   292 ns          292 ns     2339761
CEL                   3535 ns         3535 ns      194319
CEL_FlattenedMap      1164 ns         1164 ns      653968
LuaJIT                1573 ns         1573 ns      453999

The code for the benchmark is https://github.com/yangminzhu/envoy/blob/69c2d33ef46129b16028e0b2f8452c7d4caf1562/test/cel/benchmark_test.cc, the RBAC is about 5x faster than the LuaJIT in Envoy.

Can you share how you set up the ServiceEntry for the internal auth service?

I’m trying to create a similar setup. I need to authenticate users, and was thinking to set up another “service” that will act as the auth services, connecting to our external auth service and validating, while also maintaining a cache of validated users for performance. This service would run on the same cluster, but I am not sure how to call it from the Lua script, but it seems that you solved that issue?

Hi Quack90. We’ve recently switched to the standard external authorization filter (anyway we had still the need of declaring the ServiceEntry), due to the fact that we’ve found a better way to integrate the overall auth system.

something like this should fulfill you needs:

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: auth-local
spec:
  hosts:
  - auth.local
  location: MESH_INTERNAL
  ports:
  - number: 8080
    name: http
    protocol: HTTP
  resolution: STATIC
  endpoints:
  - address: 127.0.0.1

===== And in the LUA filter you need to call =====
// Building the HTTP req
local authentication_request = {
[":method"] = “GET”,
[":path"] = “/path_to_auth”,
[“Authorization”] = request_handle:headers():get(“Authorization”)
}

            local response_headers, response_body = request_handle:httpCall(
                "outbound|8080||auth.local",
                authentication_request,
                "",                             -- empty body
                5000
            )

In layman’s terms you need to use “outbound|8080||auth.local” as the target endpoint for the HTTP Call.
I hope this will help you in integrating your auth system :wink:

PS: since i’m not an Istio expert, this could not probably be the best approach. Any suggestions or recommendations are really appreciated.

Sorry, in the last answer i’ve not replied directly to you. See my previous reply for the solution :wink:

Thank you for the quick response.

I seem to have gotten something to work, so a step in the right direction.

If you don’t mind me asking, how have you been able to set up the external authorization filter? Do you have an example? Did you find any performance related improvements? I was looking into that as well, but Lua filter seemed to be easier to set up. My setup is really very basic, I just need to hit an internal service that will respond back with whether the user is authenticated or not (internally it will hit our external server and cache responses for performance).

A little extra note, I spent quite long trying to get Istio’s own JWT Auth Policy to work, as this is essentially what I need, but as my JWT provider does not allow local verification of tokens, I was not able to use this, which is why I am not trying to set this up.

Hi,
Our decision was not based on performances, but by the fact that we didn’t have to maintain a LUA script anymore. We don’t have so much requirements on the performances. In my opinion external authorization filter is more fast, due to the fact that you don’t have the script with all the stuff involved (interpretation / run of the script and so on), but is better that you ask to the experts :wink:
We use that both for the authentication and the authorization, interacting with a famous OS authorization server (i can’t say the name here), so my suggestion is also to use it to check authorizations, especially if you have complex authorization rules and so on.

We don’t use Istio JWT auth policy, all is done inside our sidecar which gets called by the envoy ext authz filter, so i cannot help. If you’ve only authentication requirements, so you want to just authenticate the user and not checking the authorizations, maybe is better to use this approach. I think that you need to configure the authorization server (maybe based on OpenID Connect?) and let Istio validate incoming tokens. But i’ve never used it so i don’t know. From what i know the ext authz filter, as the name implies, is primarily used to check also authorizations.

Here’s an example of an ext authz filter (your auth service must return 200 if user is authenticated and authorized to proceed):

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: envoy-ext-authz-http-test
spec:
  workloadLabels:
    ext-authz: "ext_authz_example" . // filter will act on pods with this label
  filters:
    - insertPosition:
        index: FIRST
      listenerMatch:
        portNumber: 8080
        listenerType: SIDECAR_INBOUND
        listenerProtocol: HTTP
      filterType: HTTP
      filterName: "envoy.ext_authz"
      filterConfig:
        http_service:
          server_uri:
            uri: http://127.0.0.1:8081
            cluster: outbound|8081||auth.local // the same cluster name you've in your ServiceEntry
            timeout: 20s
        failure_mode_allow: false
        with_request_body:
          max_request_bytes: 8192
          allow_partial_message: true

For more infos about the other properties we’ve followed the doc (it explains all the fields involved, even sometimes is not complete).
Hope it helps!

bye!

Hello!

This kind of ext_authz filter only works if ur microservice responds with a GET option right? what if my microservice only responds with POST?

The LUA filter was working as you said. Basically was performing a GET request to a local endpoint (sidecar), passing some custom headers that were needed to validate the incoming call (something like: i need to validate the access to that path with that http method).

The classical external authz filter, instead, simply forwards the request to the specified server_uri.

In our case, more specifically:

  • We’ve a sidecar which is basically composed by an authorization/authentication library released by our identity server. This library intercepts incoming http calls and performs authentication and authorization checks.
  • Once those checks are performed, the request reaches a generic endpoint handler (which handles each HTTP method) that always replies 200. This because if there are some problems in auth/authz checks an error will be already returned and the generic handler will not be reached
  • If the filter receives a 200 OK code -> it forwards the request to the target microservice

Mostly the authorization is more complex, like below auth-adapt .
I am using post, forwarding token in http header, adding source servicename/ namespacename in http request header, for validate.
Can istio envoy filter also implement such requirement?

apiVersion: config.istio.io/v1alpha2
kind: authorization
metadata:
  name: authzrequestcontext
  namespace: istio-system
  action:
    method: request.method | ""
    namespace: destination.namespace | ""
    path: request.path | ""
    properties:
      token: request.headers["authorization"] | ""
    service: destination.service.name | ""
  subject:
    groups: request.auth.principal | ""
    properties:
      namespace: source.namespace | ""
      service: source.workload.name | ""
    user: request.auth.principal | ""

I almost read all what it was written regarding how to redirect to external authentication server, and it looks not simple :frowning: , and even may impact seriously performance if, as proposed here (https://journal.arrikto.com/kubeflow-authentication-with-istio-dex-5eafdfac4782), each request will be first send to the auth. server for checking.
There is also this (https://github.com/envoyproxy/envoy/issues/8571), that proposes an additional component in the system to handle this redirection.

I would like to ask, if it is not too much. Using ‘RequestAuthentication’ we already are able to check if a valid JWT is present (https://istio.io/docs/tasks/security/authentication/authn-policy/), and it handles all the ingress traffic. At this point why to not simply add a option to redirect to the auth. server ?

So, don’t know if i’ve understood well, basically you need to perform an HTTP call passing additional headers… am i right? So, i’m not an istio expert, but this can be achieved with a LUA authorization filter (see older posts). Basically in this kind of filter i think you can code your needed behaviour.

1 Like

Hi Guys. if anyone using external_authz envoy filters were able to do “whitelisting”? So part of the request to target service are not being intercepted by filter and go straight to the target?