{"id":3045,"date":"2020-05-27T13:05:58","date_gmt":"2020-05-27T07:35:58","guid":{"rendered":"https:\/\/opstree.com\/blog\/\/?p=3045"},"modified":"2025-11-20T16:37:22","modified_gmt":"2025-11-20T11:07:22","slug":"ip-whitelisting-using-istio-policy-on-kubernetes-microservices","status":"publish","type":"post","link":"https:\/\/opstree.com\/blog\/2020\/05\/27\/ip-whitelisting-using-istio-policy-on-kubernetes-microservices\/","title":{"rendered":"IP Whitelisting Using Istio Policy On Kubernetes Microservices"},"content":{"rendered":"\r\n<p><strong>Recently<\/strong>, we explored <strong><a href=\"https:\/\/opstree.com\/blog\/\/2020\/04\/07\/preserve-source-ip-in-aws-classic-load-balancer-and-istios-envoy-using-proxy-protocol\/\">Preserving the Source IP address<\/a> on AWS Classic Loadbalancer and Istio\u2019s envoy using the proxy protocol <\/strong>in our first Part<strong>. <\/strong>Continuing to the second part of this series, we will look at How can we apply IP whitelisting on the Kubernetes microservices!<\/p>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Problem Statement:<\/h2>\r\n\r\n\r\n\r\n<p>There are some microservices behind an internet-facing loadbalancer that we want to have limited access to, based on source IP address. This will prevent our microservices from unauthorized access.<\/p>\r\n<p><!--more--><\/p>\r\n\r\n\r\n\r\n<p>The environment took for implementing this scenario:<\/p>\r\n\r\n\r\n\r\n<p><strong>1. Kubernetes Environment (Kubernetes v-1.15.3)<br \/>2. Service Mesh using Istio<\/strong><\/p>\r\n<p>\r\n\r\n<\/p>\r\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\r\n<p>This blog is divided into solution for Version 1.4 and 1.5\/1.6<\/p>\r\n<\/blockquote>\r\n<p>\r\n\r\n<\/p>\r\n<h2 class=\"wp-block-heading\">Solution For Version 1.4<\/h2>\r\n<p>\r\n\r\n<\/p>\r\n<p>In Istio&#8217;s component called Mixer, you can apply IP whitelisting using Mixer Policy. The Envoy sidecar logically calls Mixer before each request to perform precondition checks. Therefore in precondition checks, we apply a policy to restrict and allow access to our microservices.<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<p>So we have to perform these two steps to reach our goal:<br \/>1. Enabling Istio Policy.<br \/>2. Build and use policy.<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<h5 class=\"wp-block-heading\">Enabling Policy Enforcement<\/h5>\r\n<p>\r\n\r\n<\/p>\r\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\r\n<p>The mixer policy is deprecated in Istio 1.5<\/p>\r\n<\/blockquote>\r\n<p>\r\n\r\n<\/p>\r\n<p>In the default Istio installation profile, policy enforcement is disabled. To install Istio with policy enforcement on, use the\u00a0<code>--set values.global.disablePolicyChecks=false<\/code>\u00a0and\u00a0<code>--set values.pilot.policy.enabled=true<\/code>\u00a0install option.<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<p>Ex:<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<pre class=\"wp-block-code\"><code>istioctl manifest apply --set values.global.disablePolicyChecks=false --set values.pilot.policy.enable=true<\/code><\/pre>\r\n<p>\r\n\r\n<\/p>\r\n<p>Or if you already setup Istio, you can use these steps:<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<pre class=\"wp-block-code\"><code>1. kubectl -n istio-system get cm istio -o yaml &gt; policy-enforce.yaml\r\n2. vim  policy-enforce.yaml\r\n3. Update to disablePolicyChecks: true to disablePolicyChecks: false\r\n4. kubectl apply -f policy-enforce.yaml\r\n5. kubectl -n istio-system get cm istio -o jsonpath=\"{@.data.mesh}\" | grep disablePolicyChecks<\/code><\/pre>\r\n<p>\r\n\r\n<\/p>\r\n<pre class=\"wp-block-syntaxhighlighter-code\">Output:\r\ndisablePolicyChecks: false<\/pre>\r\n<p>\r\n\r\n<\/p>\r\n<h5 class=\"wp-block-heading\">Background Of Istio&#8217;s Mixer Components<\/h5>\r\n<p>\r\n\r\n<\/p>\r\n<p>I suggest going through the mixer components once before moving to create and apply policy.<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<h5 class=\"wp-block-heading\">Build And Use Policy In 1.4<\/h5>\r\n<p>\r\n\r\n<\/p>\r\n<p>Once Policy Enforcement is enabled, you have to create Istio instance, handler, and rule as follow:<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<pre class=\"wp-block-syntaxhighlighter-code\">apiVersion: config.istio.io\/v1alpha2\r\nkind: instance\r\nmetadata:\r\n  name: sourceip\r\n  namespace: istio-system\r\nspec:\r\n  compiledTemplate: listentry\r\n  params:\r\n     value: request.headers[\"x-forwarded-for\"]\r\n---\r\napiVersion: config.istio.io\/v1alpha2\r\nkind: handler\r\nmetadata:\r\n  name: whitelistip\r\n  namespace: istio-system\r\nspec:\r\n  compiledAdapter: listchecker\r\n  params:\r\n    overrides: [\"1.2.*.*\/32\",\"3.4.*.*\/32\",\"33.4.*.*\/32\"]\r\n    blacklist: false\r\n    entryType: IP_ADDRESSES\r\n---\r\napiVersion: config.istio.io\/v1alpha2\r\nkind: rule\r\nmetadata:\r\n  name: checkip\r\n  namespace: istio-system\r\nspec:\r\n  match: source.labels[\"app\"] == \"istio-ingressgateway\"\r\n  actions:\r\n  - handler: whitelistip\r\n    instances: [ sourceip ]<\/pre>\r\n<p>\r\n\r\n<\/p>\r\n<p>1. In <span class=\"has-inline-color has-medium-gray-color\">kind: instance<\/span>, we create a template of type <span class=\"has-inline-color has-medium-gray-color\">listentry<\/span> and pass the attribute <span class=\"has-inline-color has-medium-gray-color\">request.headers[&#8220;x-forwarded-for&#8221;]<\/span>. Attribute request.headers[&#8220;x-forwarded-for&#8221;] provide an actual source IP to handler and handler will use it to validate with IP addresses that we will store in adapter <span class=\"has-inline-color has-medium-gray-color\">listchecker<\/span> (in next step).<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<p>2. In <span class=\"has-inline-color has-medium-gray-color\">kind: handler<\/span>, we create an adapter of type <span class=\"has-inline-color has-medium-gray-color\">listchecker<\/span> and provide all the authorized IP addresses.<br \/><span class=\"has-inline-color has-medium-gray-color\">entryType: IP_ADDRESSES<\/span> means, we are defining that attributes which adapter get from instance are of type <span class=\"has-inline-color has-medium-gray-color\">IP_ADDRESSESS<\/span><\/p>\r\n<p>\r\n\r\n<\/p>\r\n<p>3. In <span class=\"has-inline-color has-medium-gray-color\">kind: rule<\/span>, we define a match condition, which means, when to validate the IP address in <span class=\"has-inline-color has-medium-gray-color\">handler<\/span> with IP address provided by <span class=\"has-inline-color has-medium-gray-color\">instance<\/span>.<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<p>You can check whether Instance, Handler, and Rule are created or not.<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<pre class=\"wp-block-code\"><code>1. kubectl get instance -n istio-system\r\n2. kubectl get handler -n istio-system\r\n3. kubectl get rule -n istio-system<\/code><\/pre>\r\n<p>\r\n\r\n<\/p>\r\n<h2 class=\"wp-block-heading\">Solution For Version 1.5\/1.6<\/h2>\r\n<p>\r\n\r\n<\/p>\r\n<h5 class=\"wp-block-heading\">Build And Use Policy In 1.6<\/h5>\r\n<p>\r\n\r\n<\/p>\r\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\r\n<p>Policy Enforcement is by default enabled in V-1.5\/1.6<\/p>\r\n<\/blockquote>\r\n<p>\r\n\r\n<\/p>\r\n<pre class=\"wp-block-syntaxhighlighter-code\">apiVersion: security.istio.io\/v1beta1\r\nkind: AuthorizationPolicy\r\nmetadata:\r\n name: whitelistip\r\n namespace: istio-system\r\nspec:\r\n selector:\r\n   matchLabels:\r\n     istio: ingressgateway\r\n action: ALLOW\r\n rules:\r\n - from:\r\n   - source:\r\n       ipBlocks: [\"3.55.*.*\/32\"]<\/pre>\r\n<p>\r\n\r\n<\/p>\r\n<h2 class=\"wp-block-heading\">Optional: Creating Custom HTTP Header Using Envoy Lua Filter<\/h2>\r\n<p>\r\n\r\n<\/p>\r\n<p>If your loadbalancer is behind some other proxy server like Cloudflare, you have to create custom Http header instead using &#8216;x-forwarded-for&#8217;, because the &#8216;x-forwarded-for header&#8217; contains 2 or more IPs (one of Cloudflare and other of your classic load balancer), means attribute &#8220;request.headers&#8221; contains List data type instead of the string data type.<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<p>In order to create your custom Http header, you can use Envoy Filter. Below is the example EnvoyFilter:<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<pre class=\"wp-block-syntaxhighlighter-code\">apiVersion: networking.istio.io\/v1alpha3\r\nkind: EnvoyFilter\r\nmetadata:\r\n  name: ingressgateway-user-ip\r\n  namespace: istio-system\r\nspec:\r\n  workloadLabels:\r\n    app: istio-ingressgateway\r\n  filters:\r\n    - listenerMatch:\r\n        portNumber: 443\r\n        listenerType: ANY\r\n      filterName: envoy.lua\r\n      filterType: HTTP\r\n      filterConfig:\r\n        inlineCode: |\r\n          function envoy_on_request(request_handle)\r\n            local xff_header = request_handle:headers():get(\"X-Forwarded-For\")\r\n            local first_ip = string.gmatch(xff_header, \"(%d+.%d+.%d+.%d+)\")();\r\n            request_handle:headers():add(\"x-client-ip\", first_ip);\r\n          end\r\n\r\n    - listenerMatch:\r\n        portNumber: 80\r\n        listenerType: ANY\r\n      filterName: envoy.lua\r\n      filterType: HTTP\r\n      filterConfig:\r\n        inlineCode: |\r\n          function envoy_on_request(request_handle)\r\n            local xff_header = request_handle:headers():get(\"X-Forwarded-For\")\r\n            local first_ip = string.gmatch(xff_header, \"(%d+.%d+.%d+.%d+)\")();\r\n            request_handle:headers():add(\"my-custom-header\", first_ip);\r\n          end<\/pre>\r\n<p>\r\n\r\n<\/p>\r\n<p>In the above example, I have defined function func envoy_on_request(request_handle) which will always execute when getting request on ingress-gateway proxy for ports 80 and 443. Also, I have defined a custom header named &#8220;my-customer-header&#8221; so you can use request.headers[&#8220;my-custom-header&#8221;] in listener instead of request.headers[&#8220;x-forwarded-for&#8221;].<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<p>Run the following command to check whether Lua script is applied or not on the ingress-gateway.<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<pre class=\"wp-block-code\"><code>istioctl proxy-config listeners $(kubectl get pods -n istio-system -lapp=istio-ingressgateway -o=jsonpath='{.items[0].metadata.name}') -n istio-system  -o json<\/code><\/pre>\r\n<p>\r\n\r\n<\/p>\r\n<p>Output:<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<pre class=\"wp-block-syntaxhighlighter-code\">    {\r\n        \"name\": \"0.0.0.0_80\",\r\n        \"address\": {\r\n            \"socketAddress\": {\r\n                \"address\": \"0.0.0.0\",\r\n                \"portValue\": 80\r\n         ....\r\n         ....\r\n        \r\n                            \"httpFilters\": [\r\n                                {\r\n                                    \"name\": \"envoy.lua\",\r\n                                    \"config\": {\r\n                                        \"inlineCode\": \"function envoy_on_request(request_handle)\\n  local xff_header = request_handle:headers():get(\\\"X-Forwarded-For\\\")\\n  local first_ip = string.gmatch(xff_header, \\\"(%d+.%d+.%d+.%d+)\\\")();\\n  request_handle:headers():add(\\\"my-custom-header\\\", first_ip);\\nend\\n\"\r\n                                    }\r\n                                },\r\n     ....\r\n     .....<\/pre>\r\n<p>\r\n\r\n<\/p>\r\n<p>After creating custom http header &#8220;my-custom-header&#8221;, you can use this in Policy as follow:<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<h5 class=\"wp-block-heading\">In Version 1.4<\/h5>\r\n<p>\r\n\r\n<\/p>\r\n<pre class=\"wp-block-syntaxhighlighter-code\">apiVersion: config.istio.io\/v1alpha2\r\nkind: handler\r\nmetadata:\r\n  name: whitelistip\r\n  namespace: istio-system\r\nspec:\r\n  compiledAdapter: listchecker\r\n  params:\r\n    overrides: [\"3.44.*.*\/32\"]\r\n    blacklist: false\r\n    entryType: IP_ADDRESSES\r\n---\r\napiVersion: config.istio.io\/v1alpha2\r\nkind: instance\r\nmetadata:\r\n  name: sourceip\r\n  namespace: istio-system\r\nspec:\r\n  compiledTemplate: listentry\r\n  params:\r\n   value: request.headers[\"my-custom-header\"] | \"0.0.0.0\"\r\n  \r\n---\r\napiVersion: config.istio.io\/v1alpha2\r\nkind: rule\r\nmetadata:\r\n  name: checkip\r\n  namespace: istio-system\r\nspec:\r\n  match: source.labels[\"app\"] == \"istio-ingressgateway\"\r\n  actions:\r\n  - handler: whitelistip\r\n    instances: [ sourceip ]\r\n---<\/pre>\r\n<p>\r\n\r\n<\/p>\r\n<h5 class=\"wp-block-heading\">In Version 1.5\/1.6<\/h5>\r\n<p>\r\n\r\n<\/p>\r\n<pre class=\"wp-block-syntaxhighlighter-code\">apiVersion: security.istio.io\/v1beta1\r\nkind: AuthorizationPolicy\r\nmetadata:\r\n name: whitelistip\r\n namespace: istio-system\r\nspec:\r\n selector:\r\n   matchLabels:\r\n     istio: ingressgateway\r\n action: ALLOW\r\n rules:\r\n - when:\r\n   - key: request.headers[my-custom-header]\r\n     values: [\"3.44.*.*\"]<\/pre>\r\n<p>\r\n\r\n<\/p>\r\n<h2 class=\"wp-block-heading\">What\u2019s next?<\/h2>\r\n<p>\r\n\r\n<\/p>\r\n<p>In the coming posts, we will see \u201ckibana dashboard for Ingress gateway logs using EFK\u201d.<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<div class=\"wp-block-buttons\">\r\n<div class=\"wp-block-button\">\r\n<div class=\"wp-block-buttons\">\r\n<div class=\"wp-block-button\"><strong>Related Searches &#8211; <a href=\"https:\/\/opstree.com\/services\/devsecops-transformation-and-automation\/\">DevSecOps Services<\/a> | <a href=\"https:\/\/opstree.com\/services\/cloud-engineering-services\/\" target=\"_blank\" rel=\"noopener\">Cloud Consulting Services<\/a>\u00a0<\/strong><\/div>\r\n<\/div>\r\n<\/div>\r\n<\/div>\r\n<p><!-- \/wp:buttons --><\/p>\r\n<!-- \/wp:buttons -->","protected":false},"excerpt":{"rendered":"<p>Recently, we explored Preserving the Source IP address on AWS Classic Loadbalancer and Istio\u2019s envoy using the proxy protocol in our first Part. Continuing to the second part of this series, we will look at How can we apply IP whitelisting on the Kubernetes microservices! Problem Statement: There are some microservices behind an internet-facing loadbalancer &hellip; <a href=\"https:\/\/opstree.com\/blog\/2020\/05\/27\/ip-whitelisting-using-istio-policy-on-kubernetes-microservices\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;IP Whitelisting Using Istio Policy On Kubernetes Microservices&#8221;<\/span><\/a><\/p>\n","protected":false},"author":159459904,"featured_media":29900,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_coblocks_attr":"","_coblocks_dimensions":"","_coblocks_responsive_height":"","_coblocks_accordion_ie_support":"","jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[144203,28070474],"tags":[768739294,700335988,768739308,700334984,700334985,700334979,700334982,4914082,177410,9628,386546842],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/opstree.com\/blog\/wp-content\/uploads\/2025\/11\/DevSecOps-1.jpg","jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/pfDBOm-N7","jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts\/3045"}],"collection":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/users\/159459904"}],"replies":[{"embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/comments?post=3045"}],"version-history":[{"count":27,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts\/3045\/revisions"}],"predecessor-version":[{"id":29936,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts\/3045\/revisions\/29936"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/media\/29900"}],"wp:attachment":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/media?parent=3045"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/categories?post=3045"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/tags?post=3045"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}