Adding Secure HTTP Headers via Istio Envoy Filter

What will I cover in the post?

You will see how to increase the security of your web application using Secure HTTP Headers.

Secure HTTP Headers

Secure HTTP Headers allow to increase the security of your web application in the very simple way.

The recommended Secure HTTP Headers can be found at the OWASP site.

Istio Bookinfo Demo application

In my previous post I have described how to install the Istio Bookinfo Demo application.

To continue to work with the post you should first install the Bookinfo Demo application.

Testing Secure HTTP Headers

You may test Secure HTTP Headers using this site https://securityheaders.com/

Let’s scan our Bookinfo Demo application with the site. We will see that the score is very low since the application does not use headers. By the way, I strongly recommend to check the “Hide Results” checkbox when you test your application. Not sure you want it will appear in the Hall of Shame of the site …

Adding Secure HTTP Headers via Istio Envoy Filter

I strongly recommend to read the Patrick Spiegel blog.

First off all, the blog explains why to use Security Headers and which Security Headers should be set.

In addition, it includes the very good example of the Istio Envoy Filter that allow to add the recommended Security Headers in the very simple way.

Deployment of the Istio Envoy Filter

Let’s deploy the Istio Envoy Filter (based on the blog example):

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: security-by-default-header-filter
spec:
  filters:
  - listenerMatch:
      listenerType: GATEWAY
    filterType: HTTP
    filterName: envoy.lua
    filterConfig:
      inlineCode: |
        function envoy_on_response(response_handle)
          function hasFrameAncestors(rh)
            s = rh:headers():get("Content-Security-Policy");
            delimiter = ";";
            defined = false;
            for match in (s..delimiter):gmatch("(.-)"..delimiter) do
              match = match:gsub("%s+", "");
              if match:sub(1, 15)=="frame-ancestors" then
                return true;
              end
            end
            return false;
          end
          if not response_handle:headers():get("Content-Security-Policy") then
            csp = "frame-ancestors none;";
            response_handle:headers():add("Content-Security-Policy", csp);
          elseif response_handle:headers():get("Content-Security-Policy") then
            if not hasFrameAncestors(response_handle) then
              csp = response_handle:headers():get("Content-Security-Policy");
              csp = csp .. ";frame-ancestors none;";
              response_handle:headers():replace("Content-Security-Policy", csp);
            end
          end
          if not response_handle:headers():get("X-Frame-Options") then
            response_handle:headers():add("X-Frame-Options", "deny");
          end
          if not response_handle:headers():get("X-XSS-Protection") then
            response_handle:headers():add("X-XSS-Protection", "1; mode=block");
          end
          if not response_handle:headers():get("X-Content-Type-Options") then
            response_handle:headers():add("X-Content-Type-Options", "nosniff");
          end
          if not response_handle:headers():get("Referrer-Policy") then
            response_handle:headers():add("Referrer-Policy", "no-referrer");
          end
          if not response_handle:headers():get("X-Download-Options") then
            response_handle:headers():add("X-Download-Options", "noopen");
          end
          if not response_handle:headers():get("X-DNS-Prefetch-Control") then
            response_handle:headers():add("X-DNS-Prefetch-Control", "off");
          end
          if not response_handle:headers():get("Feature-Policy") then
            response_handle:headers():add("Feature-Policy",
                                          "camera 'none';"..
                                          "microphone 'none';"..
                                          "geolocation 'none';"..
                                          "encrypted-media 'none';"..
                                          "payment 'none';"..
                                          "speaker 'none';"..
                                          "usb 'none';");
          end
          if response_handle:headers():get("X-Powered-By") then
            response_handle:headers():remove("X-Powered-By");
          end
        end
EOF

You will see the following output shows that the filter is deployed successfully:

envoyfilter.networking.istio.io/security-by-default-header-filter created

Ensure Secure HTTP Headers are added

Lets scan our application once again.

The magic is happens! The headers are added and the application has very good grade!

Take Aways

You can take the following take aways from the post:

You see that you can add HTTP Headers using Istio envoyfilter in the very easy way.

Read my next post to see how to configure secure service-to-service communication using Istio!