在应用程序中添加HTTP响应头可以提高Web应用程序的安全性。本文介绍如何通过定义EnvoyFilter添加HTTP响应头。

前提条件

背景信息

OWASP提供了最佳实践指南和编程框架,描述了如何使用安全响应头保护应用程序的安全。HTTP响应头的基准配置如下:
HTTP响应头 默认值 描述
Content-Security-Policy frame-ancestors none; 防止其他网站进行Clickjacking攻击。
X-XSS-Protection 1;mode=block 激活浏览器的XSS过滤器(如果可用),检测到XSS时阻止渲染。
X-Content-Type-Options Nosniff 禁用浏览器的内容嗅探。
Referrer-Policy no-referrer 禁用自动发送引荐来源。
X-Download-Options noopen 禁用旧版本IE中的自动打开下载功能。
X-DNS-Prefetch-Control off 禁用对页面上的外部链接的推测性DNS解析。
Server envoy 由Istio的入口网关自动设置。
X-Powered-by 无默认值 去掉该值来隐藏潜在易受攻击的应用程序服务器的名称和版本。
Feature-Policy

camera ‘none’;

microphone ‘none’;

geolocation ‘none’;

encrypted-media ‘none’;

payment ‘none’;

speaker ‘none’;

usb ‘none’;

控制可以在浏览器中使用的功能和API。
以Bookinfo应用程序为例(详情请参见部署应用到ASM实例),通过Curl命令可以看到应用程序的HTTP响应头信息如下。
curl -I http://{入口网关服务的IP地址}/productpage
HTTP/1.1 200 OK
content-type: text/html; charset=utf-8
content-length: 5183
server: istio-envoy
date: Tue, 28 Jan 2020 08:15:21 GMT
x-envoy-upstream-service-time: 28

可以看到,在默认情况下,示例应用程序的入口首页响应并没有包含上述表格中安全相关的HTTP响应头。通过Istio EnvoyFilter可以快速添加安全相关的HTTP响应头。

操作步骤

  1. 部署EnvoyFilter。
    • 如果您的ASM实例版本<v1.12.4.0-g7d140f10-aliyun,您可以执行以下命令,直接部署EnvoyFilter。
      kubectl apply -f - <<EOF
      apiVersion: networking.istio.io/v1alpha3
      kind: EnvoyFilter
      metadata:
        name: addheader-into-ingressgateway
        namespace: istio-system
        labels:
          asm-system: 'true'
          provider: asm
      spec:
        workloadSelector:
          # EnvoyFilter通过标签选择同一命名空间下的工作负载。
          labels:
            istio: ingressgateway
        configPatches:
          # 需要修改的Envoy配置。
        - applyTo: HTTP_FILTER
          match:
            context: GATEWAY
            proxy:
              proxyVersion: '^1\.9.*'
            listener:
              filterChain:
                filter:
                  name: "envoy.filters.network.http_connection_manager"
                  subFilter:
                    name: "envoy.filters.http.router"
          patch:
            operation: INSERT_BEFORE
            value: # Lua脚本配置。
              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("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
      proxyVersion:配置为您当前的Istio版本。EnvoyFilter创建时需要设置proxyVersion来指定期望作用的Istio版本范围,EnvoyFilter配置中的一些字段存在Istio版本不兼容的可能性。不同Istio版本的EnvoyFilter内容不同:
      • 如果您使用的Istio1.8及以下版本,请根据版本替换proxyVersion字段,并且替换上述EnvoyFilter中的envoy.filters.network.http_connection_managerenvoy.http_connection_managerenvoy.filters.http.routerenvoy.routertype.googleapis.com/envoy.extensions.filters.http.lua.v3.Luatype.googleapis.com/envoy.config.filter.http.lua.v2.Lua
      • 如果您使用的Istio1.9及以上版本,请根据版本替换proxyVersion字段。
    • 如果您的ASM实例版本≥v1.12.4.0-g7d140f10-aliyun,您可以通过插件市场直接部署EnvoyFilter。
      1. 登录ASM控制台
      2. 在左侧导航栏,选择服务网格 > 网格管理
      3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
      4. 在网格详情页面左侧导航栏选择插件扩展中心 > 插件市场
      5. 插件市场页面单击添加HTTP响应头,然后在插件详情页面,单击插件配置页签。
      6. 插件生效范围区域,选中网关生效,然后单击添加网关生效范围
      7. 添加网关生效范围对话框中,在选择网关区域选中ingressgateway,单击添加图标,将ingressgateway添加到已选择区域中,然后单击确定
        说明 ingressgateway为ASM默认部署的网关名称,您也可以选择希望生效添加HTTP响应头能力的其它ASM网关。
      8. 插件配置区域,删除YAML框中的所有内容,然后打开生效开关,等待插件启用。

        在插件启用后,ASM会自动创建EnvoyFilter。

  2. 执行以下Curl命令,验证安全HTTP响应头是否添加成功。
    curl -I http://{入口网关服务的IP地址}/productpage
    预期输出:
    HTTP/1.1 200 OK
    content-type: text/html; charset=utf-8
    content-length: 4183
    server: istio-envoy
    date: Tue, 28 Jan 2020 09:07:01 GMT
    x-envoy-upstream-service-time: 17
    content-security-policy: frame-ancestors none;
    x-frame-options: deny
    x-xss-protection: 1; mode=block
    x-content-type-options: nosniff
    referrer-policy: no-referrer
    x-download-options: noopen
    x-dns-prefetch-control: off
    feature-policy: camera 'none';microphone 'none';geolocation 'none';encrypted-media 'none';payment 'none';speaker 'none';usb 'none';
    由预期输出得到,示例应用程序的入口首页响应已经包含了HTTP响应头的基准配置中所描述的安全相关的HTTP响应头。

FAQ

为什么无法访问带有特殊字符的URL?

以特殊字符Á为例,特殊字符Á的编码格式采用Unicode,而不是ASCII。因此请求的URL中带有特殊字符,不符合URL规范。更多信息,请参见Envoy cannot parse non-alphanumeric characters if not urlencoded