Security headers and TLS protocol version with Envoy/Istio

HTTP security headers are HTTP response headers designed to enhance the security of a site. They instruct browsers on how to behave and prevent them from executing vulnerabilities that would endanger your users.

Whilst there are number of  HTTP security headers which can be implement to increase the security of a web site, below are few most important HTTP security headers that will strengthen your website’s security.

XSS Protection Header
The HTTP 'X-XSS-Protection' response header is a feature of modern browsers that allows websites to control their XSS auditors.The server is not configured to return a 'X-XSS-Protection' header which means that any pages on this website could be at risk of a Cross-Site Scripting (XSS) attack.

Content Security Policy (CSP)
Content Security Policy (CSP) is an HTTP response security header that developers and security architects can leverage to create an allow list of domains from which the site is allowed to load resources. This header provides an in-depth security protection from critical vulnerabilities such as cross-site scripting and clickjacking. Additionally, CSP restricts execution of inline JavaScript, dynamic JavaScript code evaluation from strings, and framing of the site from external domains. While CSP is not a replacement for input validation, it can help to significantly reduce the risk of XSS from unknown weaknesses. The CSP frame-ancestors directive is equivalent to X-Frame-Options and restricts the domain that are allowed to frame the site’s content.

HTTP Strict Transport Security
HTTP Strict Transport Security (also named HSTS) is a web security policy mechanism which helps to protect websites against protocol downgrade attacks and cookie hijacking. It allows web servers to declare that web browsers (or other complying user agents) should only interact with it using secure HTTPS connections, and never via the insecure HTTP protocol. A server implements an HSTS policy by supplying a header (Strict-Transport-Security) over an HTTPS connection (HSTS headers over HTTP are ignored).

X-Content-Type-Options
Almost all browsers are designed to use a mime sniffing technique to guess the content type of the HTTP response instead of adhering to the Content-Type specified by the application in specific cases or ignoring the content when no mime type is specified. Inconsistencies introduced by the mime sniffing techniques could allow attackers to conduct Cross-Site Scripting attacks or steal sensitive user data. The application fails to instruct the browser to strictly enforce the Content-Type specification supplied in the response.

Different servers such as Nginx, Apache, IIS has different mechanism to configure security headers. However, if you are in Kubernetes and use Istio service mesh you can easily achieve this in sidecar Envoy proxy. Benefit of that is you can add the same configuration to the all web sites irrespective of the underline web server.

In the below code I use Envoy Lua Filter to do the header manipulation through response handlers. Note that I use SIDECAR_INBOUND as context and add this filter to all my front-end web apps (Angular, React, Nextjs etc..).

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: "myweb-ef"
spec:
  workloadSelector:
    labels:
      app: myweb
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        portNumber: 80
        filterChain:
          filter:
            name: "envoy.http_connection_manager"
            subFilter:
              name: "envoy.router"
    patch:
      operation: INSERT_BEFORE
      value: # lua filter specification
       name: envoy.lua
       typed_config:
        "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
        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("Strict-Transport-Security") then
              response_handle:headers():add("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
            end
            if response_handle:headers():get("X-Powered-By") then
              response_handle:headers():remove("X-Powered-By");
            end
          end

For testing this we can use testssl.sh. You can just pull the container from dockerhub and run it as below:

docker run --rm -ti  drwetter/testssl.sh https://myweb.com.au

You can confirm the added security headers by looking at the output.

Another best practice that will strengthen your website’s security is disabling earlier versions of TLS (TLS v1.0 and 1.1) and enable only TLS v1.2 and higher. Reason behind this is earlier TLS versions including TLS v1.0 have known security issues and were decrypted using capable tools and systems resulting on data leakage on transmission. Also its important to disable the weak ciphers in TLS v1.2.

You can simply achieve both of them in Isito gateway as below. Note that I have setup both minProtocolVersion and cipherSuites.

- hosts:
    - 'myweb.com.au'
    port:
      name: https
      number: 443
      protocol: HTTPS
    tls:
      cipherSuites:
      - ECDHE-RSA-AES128-GCM-SHA256
      - ECDHE-RSA-AES256-GCM-SHA384
      credentialName: myweb-certs
      minProtocolVersion: TLSV1_2
      mode: SIMPLE

Now If you test this with testssl.sh using above docker run command, you will see the below output which will confirm the correct TLS version/ciphers.

That's It! These security header and best practices around TLS would increase the security of most websites.