local helpers = require "spec.helpers"
local ssl_fixtures = require "spec.fixtures.ssl"


local other_ca_cert = [[
-----BEGIN CERTIFICATE-----
MIIEvjCCAqagAwIBAgIJALabx/Nup200MA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV
BAMMCFlvbG80Mi4xMCAXDTE5MDkxNTE2Mjc1M1oYDzIxMTkwODIyMTYyNzUzWjAT
MREwDwYDVQQDDAhZb2xvNDIuMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
ggIBANIW67Ay0AtTeBY2mORaGet/VPL5jnBRz0zkZ4Jt7fEq3lbxYaJBnFI8wtz3
bHLtLsxkvOFujEMY7HVd+iTqbJ7hLBtK0AdgXDjf+HMmoWM7x0PkZO+3XSqyRBbI
YNoEaQvYBNIXrKKJbXIU6higQaXYszeN8r3+RIbcTIlZxy28msivEGfGTrNujQFc
r/eyf+TLHbRqh0yg4Dy/U/T6fqamGhFrjupRmOMugwF/BHMH2JHhBYkkzuZLgV2u
7Yh1S5FRlh11am5vWuRSbarnx72hkJ99rUb6szOWnJKKew8RSn3CyhXbS5cb0QRc
ugRc33p/fMucJ4mtCJ2Om1QQe83G1iV2IBn6XJuCvYlyWH8XU0gkRxWD7ZQsl0bB
8AFTkVsdzb94OM8Y6tWI5ybS8rwl8b3r3fjyToIWrwK4WDJQuIUx4nUHObDyw+KK
+MmqwpAXQWbNeuAc27FjuJm90yr/163aGuInNY5Wiz6CM8WhFNAi/nkEY2vcxKKx
irSdSTkbnrmLFAYrThaq0BWTbW2mwkOatzv4R2kZzBUOiSjRLPnbyiPhI8dHLeGs
wMxiTXwyPi8iQvaIGyN4DPaSEiZ1GbexyYFdP7sJJD8tG8iccbtJYquq3cDaPTf+
qv5M6R/JuMqtUDheLSpBNK+8vIe5e3MtGFyrKqFXdynJtfHVAgMBAAGjEzARMA8G
A1UdEwQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggIBAK0BmL5B1fPSMbFy8Hbc
/ESEunt4HGaRWmZZSa/aOtTjhKyDXLLJZz3C4McugfOf9BvvmAOZU4uYjfHTnNH2
Z3neBkdTpQuJDvrBPNoCtJns01X/nuqFaTK/Tt9ZjAcVeQmp51RwhyiD7nqOJ/7E
Hp2rC6gH2ABXeexws4BDoZPoJktS8fzGWdFBCHzf4mCJcb4XkI+7GTYpglR818L3
dMNJwXeuUsmxxKScBVH6rgbgcEC/6YwepLMTHB9VcH3X5VCfkDIyPYLWmvE0gKV7
6OU91E2Rs8PzbJ3EuyQpJLxFUQp8ohv5zaNBlnMb76UJOPR6hXfst5V+e7l5Dgwv
Dh4CeO46exmkEsB+6R3pQR8uOFtubH2snA0S3JA1ji6baP5Y9Wh9bJ5McQUgbAPE
sCRBFoDLXOj3EgzibohC5WrxN3KIMxlQnxPl3VdQvp4gF899mn0Z9V5dAsGPbxRd
quE+DwfXkm0Sa6Ylwqrzu2OvSVgbMliF3UnWbNsDD5KcHGIaFxVC1qkwK4cT3pyS
58i/HAB2+P+O+MltQUDiuw0OSUFDC0IIjkDfxLVffbF+27ef9C5NG81QlwTz7TuN
zeigcsBKooMJTszxCl6dtxSyWTj7hJWXhy9pXsm1C1QulG6uT4RwCa3m0QZoO7G+
6Wu6lP/kodPuoNubstIuPdi2
-----END CERTIFICATE-----
]]

local fixtures = {
  http_mock = {
    upstream_mtls = [[
      server {
          server_name example.com;
          listen 16798 ssl;

          ssl_certificate        ../spec/fixtures/mtls_certs/example.com.crt;
          ssl_certificate_key    ../spec/fixtures/mtls_certs/example.com.key;
          ssl_client_certificate ../spec/fixtures/mtls_certs/ca.crt;
          ssl_verify_client      on;
          ssl_session_tickets    off;
          ssl_session_cache      off;
          keepalive_requests     0;

          location = / {
              add_header 'X-Cert' $ssl_client_escaped_cert;
              echo 'it works';
          }
      }
    ]],
    upstream_tls = [[
      server {
          server_name example.com;
          listen 16799 ssl;

          ssl_certificate        ../spec/fixtures/mtls_certs/example.com.crt;
          ssl_certificate_key    ../spec/fixtures/mtls_certs/example.com.key;
          ssl_session_tickets    off;
          ssl_session_cache      off;
          keepalive_requests     0;

          location = / {
              echo 'it works';
          }
      }
    ]]
  },
}


local function reload_router(flavor)
  helpers = require("spec.internal.module").reload_helpers(flavor)

  fixtures.dns_mock = helpers.dns_mock.new({ mocks_only = true })
  fixtures.dns_mock:A {
    name = "example.com",
    address = "127.0.0.1",
  }
end


-- TODO: remove it when we confirm it is not needed
local function gen_route(flavor, r)
  return r
end

local function gen_plugin(route)
  return {
    name = "pre-function",
    route = { id = route.id },
    config = {
      access = {
        [[
          kong.service.request.enable_buffering()
        ]]
      }
    }
  }
end

local function get_proxy_client(subsystems, stream_port)
  if subsystems == "http" then
    return assert(helpers.proxy_client())
  else
     return assert(helpers.proxy_client(20000, stream_port))
  end
end

local function wait_for_all_config_update(subsystems)
  local opt = {}
  if subsystems == "stream" then
    opt.stream_enabled = true
    opt.stream_port = 19003
  end

  helpers.wait_for_all_config_update(opt)
end

for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions" }) do
for _, strategy in helpers.each_strategy() do
  describe("overriding upstream TLS parameters for database [#" .. strategy .. ", flavor = " .. flavor .. "]", function()
    local admin_client
    local bp
    local service_mtls, service_tls
    local certificate, certificate_bad, ca_certificate
    local upstream
    local service_mtls_upstream

    local tls_service_mtls, tls_service_tls
    local tls_upstream
    local tls_service_mtls_upstream

    local route_mtls_buffered_proxying, route_tls_buffered_proxying, route_mtls_upstream_buffered_proxying

    reload_router(flavor)

    lazy_setup(function()
      bp = helpers.get_db_utils(strategy, {
        "routes",
        "services",
        "certificates",
        "ca_certificates",
        "upstreams",
        "targets",
      })

      service_mtls = assert(bp.services:insert({
        name = "protected-service-mtls",
        url = "https://127.0.0.1:16798/",
      }))

      service_tls = assert(bp.services:insert({
        name = "protected-service",
        url = "https://example.com:16799/", -- domain name needed for hostname check
      }))

      upstream = assert(bp.upstreams:insert({
        name = "backend-mtls",
      }))

      assert(bp.targets:insert({
        upstream = { id = upstream.id, },
        target = "127.0.0.1:16798",
      }))

      service_mtls_upstream = assert(bp.services:insert({
        name = "protected-service-mtls-upstream",
        url = "https://backend-mtls/",
      }))

      certificate = assert(bp.certificates:insert({
        cert = ssl_fixtures.cert_client,
        key = ssl_fixtures.key_client,
      }))

      certificate_bad = assert(bp.certificates:insert({
        cert = ssl_fixtures.cert, -- this cert is *not* trusted by upstream
        key = ssl_fixtures.key,
      }))

      ca_certificate = assert(bp.ca_certificates:insert({
        cert = ssl_fixtures.cert_ca,
      }))

      assert(bp.routes:insert(gen_route(flavor,{
        service = { id = service_mtls.id, },
        hosts = { "example.com", },
        paths = { "/mtls", },
      })))

      assert(bp.routes:insert(gen_route(flavor,{
        service = { id = service_tls.id, },
        hosts = { "example.com", },
        paths = { "/tls", },
      })))

      assert(bp.routes:insert(gen_route(flavor,{
        service = { id = service_mtls_upstream.id, },
        hosts = { "example.com", },
        paths = { "/mtls-upstream", },
      })))

      route_mtls_buffered_proxying = assert(bp.routes:insert(gen_route(flavor,{
        service = { id = service_mtls.id, },
        hosts = { "example.com", },
        paths = { "/mtls-buffered-proxying", },
      })))

      route_tls_buffered_proxying = assert(bp.routes:insert(gen_route(flavor,{
        service = { id = service_tls.id, },
        hosts = { "example.com", },
        paths = { "/tls-buffered-proxying", },
      })))

      route_mtls_upstream_buffered_proxying = assert(bp.routes:insert(gen_route(flavor,{
        service = { id = service_mtls_upstream.id, },
        hosts = { "example.com", },
        paths = { "/mtls-upstream-buffered-proxying", },
      })))

      -- use pre-function to enable buffered_proxying in order to trigger the
      -- `ngx.location.capture("/kong_buffered_http")` in `Kong.response()`
      assert(bp.plugins:insert(gen_plugin(route_mtls_buffered_proxying)))
      assert(bp.plugins:insert(gen_plugin(route_tls_buffered_proxying)))
      assert(bp.plugins:insert(gen_plugin(route_mtls_upstream_buffered_proxying)))

      -- tls
      tls_service_mtls = assert(bp.services:insert({
        name = "tls-protected-service-mtls",
        url = "tls://127.0.0.1:16798",
      }))

      tls_service_tls = assert(bp.services:insert({
        name = "tls-protected-service",
        url = "tls://example.com:16799", -- domain name needed for hostname check
      }))

      tls_upstream = assert(bp.upstreams:insert({
        name = "tls-backend-mtls",
      }))

      assert(bp.targets:insert({
        upstream = { id = tls_upstream.id, },
        target = "example.com:16798",
      }))

      tls_service_mtls_upstream = assert(bp.services:insert({
        name = "tls-protected-service-mtls-upstream",
        url = "tls://tls-backend-mtls",
        host = "example.com"
      }))

      assert(bp.routes:insert(gen_route(flavor,{
        service = { id = tls_service_mtls.id, },
        destinations = {
          {
            port = 19000,
          },
        },
        protocols = {
          "tls",
        },
      })))

      assert(bp.routes:insert(gen_route(flavor,{
        service = { id = tls_service_tls.id, },
        destinations = {
          {
            port = 19001,
          },
        },
        protocols = {
          "tls",
        },
      })))

      assert(bp.routes:insert(gen_route(flavor,{
        service = { id = tls_service_mtls_upstream.id, },
        destinations = {
          {
            port = 19002,
          },
        },
        protocols = {
          "tls",
        },
      })))


      assert(helpers.start_kong({
        router_flavor = flavor,
        database   = strategy,
        nginx_conf = "spec/fixtures/custom_nginx.template",
        stream_listen = helpers.get_proxy_ip(false) .. ":19000,"
                     .. helpers.get_proxy_ip(false) .. ":19001,"
                     .. helpers.get_proxy_ip(false) .. ":19002,"
                     .. helpers.get_proxy_ip(false) .. ":19003",
      }, nil, nil, fixtures))

      admin_client = assert(helpers.admin_client())
    end)

    lazy_teardown(function()
      helpers.stop_kong()
    end)

    local function get_tls_service_id(subsystems)
      if subsystems == "http" then
        return service_mtls.id
      else
        return tls_service_mtls.id
      end
    end

    for _, subsystems in pairs({"http", "stream"}) do
    describe(subsystems .. " mutual TLS authentication against upstream with Service object", function()
      describe("no client certificate supplied", function()
        it("accessing protected upstream", function()
          local proxy_client = get_proxy_client(subsystems, 19000)
          local res = assert(proxy_client:send {
            path    = "/mtls",
            headers = {
              ["Host"] = "example.com",
            }
          })

          local body = assert.res_status(400, res)
          assert.matches("400 No required SSL certificate was sent", body, nil, true)
          assert(proxy_client:close())
        end)

        -- buffered_proxying
        if subsystems == "http" then
          it("accessing protected upstream, buffered_proxying = true", function()
            local proxy_client = get_proxy_client(subsystems, 19000)
            local res = assert(proxy_client:send {
              path    = "/mtls-buffered-proxying",
              headers = {
                ["Host"] = "example.com",
              }
            })

            local body = assert.res_status(400, res)
            assert.matches("400 No required SSL certificate was sent", body, nil, true)
            assert(proxy_client:close())
          end)
        end
      end)

      describe(subsystems .. " #db client certificate supplied via service.client_certificate", function()
        lazy_setup(function()
          local service_id = get_tls_service_id(subsystems)
          local res = assert(admin_client:patch("/services/" .. service_id, {
            body = {
              client_certificate = { id = certificate.id, },
            },
            headers = { ["Content-Type"] = "application/json" },
          }))
          assert.res_status(200, res)
        end)

        it("accessing protected upstream", function()
          helpers.wait_until(function()
            local proxy_client = get_proxy_client(subsystems, 19000)
            local path
            if subsystems == "http" then
              path = "/mtls"
            else
              path = "/"
            end
            local res = assert(proxy_client:send {
              path    = path,
              headers = {
                ["Host"] = "example.com",
              }
            })

            return pcall(function()
              local body = assert.res_status(200, res)
              assert.equals("it works", body)
              assert(proxy_client:close())
            end)
          end, 10)
        end)

        -- buffered_proxying
        if subsystems == "http" then
          it("accessing protected upstream, buffered_proxying = true", function()
            helpers.wait_until(function()
              local proxy_client = get_proxy_client(subsystems, 19000)
              local path = "/mtls-buffered-proxying"
              local res = assert(proxy_client:send {
                path    = path,
                headers = {
                  ["Host"] = "example.com",
                }
              })

              return pcall(function()
                local body = assert.res_status(200, res)
                assert.equals("it works", body)
                assert(proxy_client:close())
              end)
            end, 10)
          end)
        end

        it("send updated client certificate", function ()
          local proxy_client = get_proxy_client(subsystems, 19000)
          local path
          if subsystems == "http" then
            path = "/mtls"
          else
            path = "/"
          end
          local res = assert(proxy_client:send {
            path    = path,
            headers = {
              ["Host"] = "example.com",
            }
          })
          assert.res_status(200, res)
          local res_cert = res.headers["X-Cert"]
          assert(proxy_client:close())

          -- buffered_proxying
          local res_cert_buffered
          if subsystems == "http" then
            local proxy_client = get_proxy_client(subsystems, 19000)
            local res = assert(proxy_client:send {
              path    = "/mtls-buffered-proxying",
              headers = {
                ["Host"] = "example.com",
              }
            })
            assert.res_status(200, res)
            res_cert_buffered = res.headers["X-Cert"]
            assert(proxy_client:close())
          end

          res = admin_client:patch("/certificates/" .. certificate.id, {
            body = {
              cert = ssl_fixtures.cert_client2,
              key = ssl_fixtures.key_client2,
            },
            headers = { ["Content-Type"] = "application/json" }
          })
          assert.res_status(200, res)

          wait_for_all_config_update(subsystems)

          local proxy_client2 = get_proxy_client(subsystems, 19000)
          if subsystems == "http" then
            path = "/mtls"
          else
            path = "/"
          end
          res = assert(proxy_client2:send {
            path    = path,
            headers = {
              ["Host"] = "example.com",
            }
          })
          assert.res_status(200, res)
          local res_cert2 = res.headers["X-Cert"]
          assert.not_equals(res_cert, res_cert2)

          -- buffered_proxying
          local res_cert2_buffered
          if subsystems == "http" then
            res = assert(proxy_client2:send {
              path    = "/mtls-buffered-proxying",
              headers = {
                ["Host"] = "example.com",
              }
            })
            assert.res_status(200, res)
            res_cert2_buffered = res.headers["X-Cert"]
            assert.not_equals(res_cert_buffered, res_cert2_buffered)
          end

          -- restore old
          res = admin_client:patch("/certificates/" .. certificate.id, {
            body = {
              cert = ssl_fixtures.cert_client,
              key = ssl_fixtures.key_client,
            },
            headers = { ["Content-Type"] = "application/json" }
          })
          assert.res_status(200, res)
          assert(proxy_client2:close())
        end)

        it("remove client_certificate removes access", function()
          local service_id = get_tls_service_id(subsystems)
          local res = assert(admin_client:patch("/services/" .. service_id, {
            body = {
              client_certificate = ngx.null,
            },
            headers = { ["Content-Type"] = "application/json" },
          }))

          assert.res_status(200, res)

          local body
          helpers.wait_until(function()
            local proxy_client= get_proxy_client(subsystems, 19000)
            res = assert(proxy_client:send {
              path    = "/mtls",
              headers = {
                ["Host"] = "example.com",
              }
            })

            return pcall(function()
              body = assert.res_status(400, res)
              assert(proxy_client:close())
            end)
          end, 10)

          assert.matches("400 No required SSL certificate was sent", body, nil, true)

          -- buffered_proxying
          if subsystems == "http" then
            helpers.wait_until(function()
              local proxy_client= get_proxy_client(subsystems, 19000)
              res = assert(proxy_client:send {
                path    = "/mtls-buffered-proxying",
                headers = {
                  ["Host"] = "example.com",
                }
              })

              return pcall(function()
                body = assert.res_status(400, res)
                assert(proxy_client:close())
              end)
            end, 10)

            assert.matches("400 No required SSL certificate was sent", body, nil, true)
          end
        end)
      end)
    end)

    describe(subsystems .. " mutual TLS authentication against upstream with Upstream object", function()
      describe("no client certificate supplied", function()
        it("accessing protected upstream", function()
          local proxy_client= get_proxy_client(subsystems, 19002)
          local res = assert(proxy_client:send {
            path    = "/mtls-upstream",
            headers = {
              ["Host"] = "example.com",
            }
          })

          local body = assert.res_status(400, res)
          assert.matches("400 No required SSL certificate was sent", body, nil, true)
          assert(proxy_client:close())
        end)

        -- buffered_proxying
        if subsystems == "http" then
          it("accessing protected upstream, buffered_proxying = true", function()
            local proxy_client= get_proxy_client(subsystems, 19002)
            local res = assert(proxy_client:send {
              path    = "/mtls-upstream-buffered-proxying",
              headers = {
                ["Host"] = "example.com",
              }
            })

            local body = assert.res_status(400, res)
            assert.matches("400 No required SSL certificate was sent", body, nil, true)
            assert(proxy_client:close())
          end)
        end
      end)

      describe("#db client certificate supplied via upstream.client_certificate", function()
        lazy_setup(function()
          local upstream_id
          if subsystems == "http" then
             upstream_id = upstream.id
          else
            upstream_id = tls_upstream.id
          end
          local res = assert(admin_client:patch("/upstreams/" .. upstream_id, {
            body = {
              client_certificate = { id = certificate.id, },
            },
            headers = { ["Content-Type"] = "application/json" },
          }))

          assert.res_status(200, res)
        end)

        it("accessing protected upstream", function()
          helpers.wait_until(function()
            local proxy_client = get_proxy_client(subsystems, 19002)
            local path
            if subsystems == "http" then
              path = "/mtls-upstream"
            else
              path = "/"
            end
            local res = assert(proxy_client:send {
              path    = path,
              headers = {
                ["Host"] = "example.com",
              }
            })

            return pcall(function()
              local body = assert.res_status(200, res)
              assert.equals("it works", body)
              assert(proxy_client:close())
            end)
          end, 10)
        end)

        -- buffered_proxying
        if subsystems == "http" then
          it("accessing protected upstream, buffered_proxying = true", function()
            helpers.wait_until(function()
              local proxy_client = get_proxy_client(subsystems, 19002)
              local path = "/mtls-upstream-buffered-proxying"
              local res = assert(proxy_client:send {
                path    = path,
                headers = {
                  ["Host"] = "example.com",
                }
              })

              return pcall(function()
                local body = assert.res_status(200, res)
                assert.equals("it works", body)
                assert(proxy_client:close())
              end)
            end, 10)
          end)
        end

        it("remove client_certificate removes access", function()
          local upstream_id
          if subsystems == "http" then
             upstream_id = upstream.id
          else
            upstream_id = tls_upstream.id
          end
          local res = assert(admin_client:patch("/upstreams/" .. upstream_id, {
            body = {
              client_certificate = ngx.null,
            },
            headers = { ["Content-Type"] = "application/json" },
          }))

          assert.res_status(200, res)

          wait_for_all_config_update(subsystems)

          local body
          helpers.wait_until(function()
            local proxy_client = get_proxy_client(subsystems, 19002)
            res = assert(proxy_client:send {
              path    = "/mtls-upstream",
              headers = {
                ["Host"] = "example.com",
              }
            })

            return pcall(function()
              body = assert.res_status(400, res)
              assert(proxy_client:close())
            end)
          end, 10)

          assert.matches("400 No required SSL certificate was sent", body, nil, true)

          -- buffered_proxying
          if subsystems == "http" then
            helpers.wait_until(function()
              local proxy_client = get_proxy_client(subsystems, 19002)
              res = assert(proxy_client:send {
                path    = "/mtls-upstream-buffered-proxying",
                headers = {
                  ["Host"] = "example.com",
                }
              })

              return pcall(function()
                body = assert.res_status(400, res)
                assert(proxy_client:close())
              end)
            end, 10)

            assert.matches("400 No required SSL certificate was sent", body, nil, true)
          end
        end)
      end)

      describe("#db when both Service.client_certificate and Upstream.client_certificate are set, Service.client_certificate takes precedence", function()
        lazy_setup(function()
          local upstream_id
          local service_mtls_upstream_id
          if subsystems == "http" then
            upstream_id = upstream.id
            service_mtls_upstream_id = service_mtls_upstream.id
          else
            upstream_id = tls_upstream.id
            service_mtls_upstream_id = tls_service_mtls_upstream.id
          end
          local res = assert(admin_client:patch("/upstreams/" .. upstream_id, {
            body = {
              client_certificate = { id = certificate_bad.id, },
            },
            headers = { ["Content-Type"] = "application/json" },
          }))

          assert.res_status(200, res)

          res = assert(admin_client:patch("/services/" .. service_mtls_upstream_id, {
            body = {
              client_certificate = { id = certificate.id, },
            },
            headers = { ["Content-Type"] = "application/json" },
          }))

          assert.res_status(200, res)

          wait_for_all_config_update(subsystems)
        end)

        it("access is allowed because Service.client_certificate overrides Upstream.client_certificate", function()
          helpers.wait_until(function()
            local proxy_client = get_proxy_client(subsystems, 19002)
            local path
            if subsystems == "http" then
              path = "/mtls-upstream"
            else
              path = "/"
            end
            local res = assert(proxy_client:send {
              path    = path,
              headers = {
                ["Host"] = "example.com",
              }
            })

            return pcall(function()
              local body = assert.res_status(200, res)
              assert.equals("it works", body)
              assert(proxy_client:close())
            end)
          end, 10)
        end)

        -- buffered_proxying
        if subsystems == "http" then
          it("access is allowed because Service.client_certificate overrides Upstream.client_certificate, buffered_proxy = true", function()
            helpers.wait_until(function()
              local proxy_client = get_proxy_client(subsystems, 19002)
              local path = "/mtls-upstream-buffered-proxying"
              local res = assert(proxy_client:send {
                path    = path,
                headers = {
                  ["Host"] = "example.com",
                }
              })

              return pcall(function()
                local body = assert.res_status(200, res)
                assert.equals("it works", body)
                assert(proxy_client:close())
              end)
            end, 10)
          end)
        end
      end)
    end)

    describe(subsystems .. " TLS verification options against upstream", function()
      describe("tls_verify", function()
        it("default is off", function()
          local proxy_client = get_proxy_client(subsystems, 19001)
          local path
          if subsystems == "http" then
            path = "/tls"
          else
            path = "/"
          end
          local res = proxy_client:send {
            path    = path,
            headers = {
              ["Host"] = "example.com",
            }
          }
          local body = assert.res_status(200, res)
          assert.equals("it works", body)
          assert(proxy_client:close())
        end)

        -- buffered_proxying
        if subsystems == "http" then
          it("default is off, buffered_proxying = true", function()
            local proxy_client = get_proxy_client(subsystems, 19001)
            local path = "/tls-buffered-proxying"
            local res = proxy_client:send {
              path    = path,
              headers = {
                ["Host"] = "example.com",
              }
            }
            local body = assert.res_status(200, res)
            assert.equals("it works", body)
            assert(proxy_client:close())
          end)
        end

        it("#db turn it on, request is blocked", function()
          local service_tls_id
          if subsystems == "http" then
            service_tls_id = service_tls.id
          else
            service_tls_id = tls_service_tls.id
          end
          local res = assert(admin_client:patch("/services/" .. service_tls_id, {
            body = {
              tls_verify = true,
            },
            headers = { ["Content-Type"] = "application/json" },
          }))

          assert.res_status(200, res)

          wait_for_all_config_update(subsystems)

          local body
          helpers.wait_until(function()
            local proxy_client = get_proxy_client(subsystems, 19001)
            local err
            res, err = proxy_client:send {
              path    = "/tls",
              headers = {
                ["Host"] = "example.com",
              }
            }
            if subsystems == "http" then
              return pcall(function()
                body = assert.res_status(502, res)
                assert(proxy_client:close())
              end)
            else
              return pcall(function()
                assert.equals("connection reset by peer", err)
                assert(proxy_client:close())
              end)
            end
          end, 10)

          if subsystems == "http" then
            assert.matches("An invalid response was received from the upstream server", body)
          end

          -- buffered_proxying
          if subsystems == "http" then
            helpers.wait_until(function()
              local proxy_client = get_proxy_client(subsystems, 19001)
              res = proxy_client:send {
                path    = "/tls-buffered-proxying",
                headers = {
                  ["Host"] = "example.com",
                }
              }
              return pcall(function()
                body = assert.res_status(502, res)
                assert(proxy_client:close())
              end)
            end, 10)

            assert.matches("An invalid response was received from the upstream server", body)
          end
        end)
      end)

      describe("ca_certificates", function()
        it("#db request is allowed through once correct CA certificate is set", function()
          local service_tls_id
          if subsystems == "http" then
            service_tls_id = service_tls.id
          else
            service_tls_id = tls_service_tls.id
          end
          local res = assert(admin_client:patch("/services/" .. service_tls_id, {
            body = {
              tls_verify = true,
              ca_certificates = { ca_certificate.id, },
            },
            headers = { ["Content-Type"] = "application/json" },
          }))

          assert.res_status(200, res)

          wait_for_all_config_update(subsystems)

          local body
          helpers.wait_until(function()
            local proxy_client = get_proxy_client(subsystems, 19001)
            local path
            if subsystems == "http" then
              path = "/tls"
            else
              path = "/"
            end
            local res = proxy_client:send {
              path    = path,
              headers = {
                ["Host"] = "example.com",
              }
            }
            return pcall(function()
              body = assert.res_status(200, res)
              assert(proxy_client:close())
            end)
          end, 10)

          assert.equals("it works", body)

          -- buffered_proxying
          if subsystems == "http" then
            helpers.wait_until(function()
              local proxy_client = get_proxy_client(subsystems, 19001)
              local path = "/tls-buffered-proxying"
              local res = proxy_client:send {
                path    = path,
                headers = {
                  ["Host"] = "example.com",
                }
              }
              return pcall(function()
                body = assert.res_status(200, res)
                assert(proxy_client:close())
              end)
            end, 10)

            assert.equals("it works", body)
          end
        end)

        it("#db request is not allowed through once the CA certificate is updated to other ca", function()
          local res = assert(admin_client:patch("/ca_certificates/" .. ca_certificate.id, {
            body = {
              cert = other_ca_cert,
            },
            headers = { ["Content-Type"] = "application/json" },
          }))

          assert.res_status(200, res)

          wait_for_all_config_update(subsystems)

          local body
          helpers.wait_until(function()
            local proxy_client = get_proxy_client(subsystems, 19001)
            local path
            if subsystems == "http" then
              path = "/tls"
            else
              path = "/"
            end
            local res, err = proxy_client:send {
              path    = path,
              headers = {
                ["Host"] = "example.com",
              }
            }

            if subsystems == "http" then
              return pcall(function()
                body = assert.res_status(502, res)
                assert(proxy_client:close())
              end)
            else
              return pcall(function()
                assert.equals("connection reset by peer", err)
                assert(proxy_client:close())
              end)
            end
          end, 10)

          if subsystems == "http" then
            assert.matches("An invalid response was received from the upstream server", body)
          end

          -- buffered_proxying
          if subsystems == "http" then
            helpers.wait_until(function()
              local proxy_client = get_proxy_client(subsystems, 19001)
              local path = "/tls-buffered-proxying"
              local res = proxy_client:send {
                path    = path,
                headers = {
                  ["Host"] = "example.com",
                }
              }

              return pcall(function()
                 body = assert.res_status(502, res)
                 assert(proxy_client:close())
              end)
            end, 10)
            assert.matches("An invalid response was received from the upstream server", body)
          end
        end)

        it("#db request is allowed through once the CA certificate is updated back to the correct ca", function()
          local res = assert(admin_client:patch("/ca_certificates/" .. ca_certificate.id, {
            body = {
              cert = ssl_fixtures.cert_ca,
            },
            headers = { ["Content-Type"] = "application/json" },
          }))

          assert.res_status(200, res)

          wait_for_all_config_update(subsystems)

          local body
          helpers.wait_until(function()
            local proxy_client = get_proxy_client(subsystems, 19001)
            local path
            if subsystems == "http" then
              path = "/tls"
            else
              path = "/"
            end
            local res = proxy_client:send {
              path    = path,
              headers = {
                ["Host"] = "example.com",
              }
            }

            return pcall(function()
              body = assert.res_status(200, res)
              assert(proxy_client:close())
            end)
          end, 10)

          assert.equals("it works", body)

          -- buffered_proxying
          if subsystems == "http" then
            helpers.wait_until(function()
              local proxy_client = get_proxy_client(subsystems, 19001)
              local path = "/tls-buffered-proxying"
              local res = proxy_client:send {
                path    = path,
                headers = {
                  ["Host"] = "example.com",
                }
              }

              return pcall(function()
                body = assert.res_status(200, res)
                assert(proxy_client:close())
              end)
            end, 10)
            assert.equals("it works", body)
          end
        end)
      end)

      describe("#db tls_verify_depth", function()
        lazy_setup(function()
          local service_tls_id
          if subsystems == "http" then
            service_tls_id = service_tls.id
          else
            service_tls_id = tls_service_tls.id
          end
          local res = assert(admin_client:patch("/services/" .. service_tls_id, {
            body = {
              tls_verify = true,
              ca_certificates = { ca_certificate.id, },
            },
            headers = { ["Content-Type"] = "application/json" },
          }))

          assert.res_status(200, res)

          wait_for_all_config_update(subsystems)

        end)

        it("request is not allowed through if depth limit is too low", function()
          local service_tls_id
          if subsystems == "http" then
            service_tls_id = service_tls.id
          else
            service_tls_id = tls_service_tls.id
          end
          local res = assert(admin_client:patch("/services/" .. service_tls_id, {
            body = {
              tls_verify_depth = 0,
            },
            headers = { ["Content-Type"] = "application/json" },
          }))

          assert.res_status(200, res)

          wait_for_all_config_update(subsystems)

          local body
          helpers.wait_until(function()
            local proxy_client = get_proxy_client(subsystems, 19001)
            local res, err = proxy_client:send {
              path    = "/tls",
              headers = {
                ["Host"] = "example.com",
              }
            }

            if subsystems == "http" then
              return pcall(function()
                body = assert.res_status(502, res)
                assert(proxy_client:close())
              end)
            else
              return pcall(function()
                assert.equals("connection reset by peer", err)
                assert(proxy_client:close())
              end)
            end
          end, 10)
          if subsystems == "http" then
            assert.matches("An invalid response was received from the upstream server", body)
          end

          -- buffered_proxying
          if subsystems == "http" then
            helpers.wait_until(function()
              local proxy_client = get_proxy_client(subsystems, 19001)
              local res = proxy_client:send {
                path    = "/tls-buffered-proxying",
                headers = {
                  ["Host"] = "example.com",
                }
              }

              return pcall(function()
                 body = assert.res_status(502, res)
                 assert(proxy_client:close())
              end)
            end, 10)
            assert.matches("An invalid response was received from the upstream server", body)
          end
        end)

        it("request is allowed through if depth limit is sufficient", function()
          local service_tls_id
          if subsystems == "http" then
            service_tls_id = service_tls.id
          else
            service_tls_id = tls_service_tls.id
          end
          local res = assert(admin_client:patch("/services/" .. service_tls_id, {
            body = {
              tls_verify_depth = 1,
            },
            headers = { ["Content-Type"] = "application/json" },
          }))

          assert.res_status(200, res)

          wait_for_all_config_update(subsystems)

          local body
          helpers.wait_until(function()
            local proxy_client = get_proxy_client(subsystems, 19001)
            local path
            if subsystems == "http" then
              path = "/tls"
            else
              path = "/"
            end
            res = assert(proxy_client:send {
              path    = path,
              headers = {
                ["Host"] = "example.com",
              }
            })

            return pcall(function()
              body = assert.res_status(200, res)
              assert(proxy_client:close())
            end)
          end, 10)

          assert.equals("it works", body)

          -- buffered_proxying
          if subsystems == "http" then
            helpers.wait_until(function()
              local proxy_client = get_proxy_client(subsystems, 19001)
              local path = "/tls-buffered-proxying"
              res = assert(proxy_client:send {
                path    = path,
                headers = {
                  ["Host"] = "example.com",
                }
              })

              return pcall(function()
                body = assert.res_status(200, res)
                assert(proxy_client:close())
              end)
            end, 10)

            assert.equals("it works", body)
          end
        end)
      end)
    end)
  end
  end)
end
end   -- for flavor

for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions" }) do
for _, strategy in helpers.each_strategy() do
  describe("overriding upstream TLS parameters for database [#" .. strategy .. ", flavor = " .. flavor .. "] (nginx_proxy_proxy_ssl_verify: on, nginx_sproxy_proxy_ssl_verify: on)", function()
    local admin_client
    local bp
    local service_tls
    local tls_service_tls
    local route_tls_buffered_proxying

    reload_router(flavor)

    lazy_setup(function()
      bp = helpers.get_db_utils(strategy, {
        "routes",
        "services",
        "certificates",
        "ca_certificates",
        "upstreams",
        "targets",
      })

      service_tls = assert(bp.services:insert({
        name = "protected-service",
        url = "https://example.com:16799/", -- domain name needed for hostname check
      }))

      assert(bp.routes:insert(gen_route(flavor,{
        service = { id = service_tls.id, },
        hosts = { "example.com", },
        paths = { "/tls", },
      })))

      route_tls_buffered_proxying = assert(bp.routes:insert(gen_route(flavor,{
        service = { id = service_tls.id, },
        hosts = { "example.com", },
        paths = { "/tls-buffered-proxying", },
      })))

      -- use pre-function to enable buffered_proxying in order to trigger the
      -- `ngx.location.capture("/kong_buffered_http")` in `Kong.response()`
      assert(bp.plugins:insert(gen_plugin(route_tls_buffered_proxying)))

      -- tls
      tls_service_tls = assert(bp.services:insert({
        name = "tls-protected-service",
        url = "tls://example.com:16799", -- domain name needed for hostname check
      }))

      assert(bp.routes:insert(gen_route(flavor,{
        service = { id = tls_service_tls.id, },
        destinations = {
          {
            port = 19001,
          },
        },
        protocols = {
          "tls",
        },
      })))

      assert(helpers.start_kong({
        router_flavor = flavor,
        database   = strategy,
        nginx_conf = "spec/fixtures/custom_nginx.template",
        stream_listen = helpers.get_proxy_ip(false) .. ":19000,"
                     .. helpers.get_proxy_ip(false) .. ":19001,"
                     .. helpers.get_proxy_ip(false) .. ":19002,"
                     .. helpers.get_proxy_ip(false) .. ":19003",
        nginx_proxy_proxy_ssl_verify = "on",
        -- An unrelated ca, just used as a placeholder to prevent nginx from reporting errors
        nginx_proxy_proxy_ssl_trusted_certificate = "../spec/fixtures/kong_clustering_ca.crt",
        nginx_sproxy_proxy_ssl_verify = "on",
        nginx_sproxy_proxy_ssl_trusted_certificate = "../spec/fixtures/kong_clustering_ca.crt",
      }, nil, nil, fixtures))

      admin_client = assert(helpers.admin_client())
    end)

    lazy_teardown(function()
      helpers.stop_kong()
    end)

    for _, subsystems in pairs({"http", "stream"}) do
    describe(subsystems .. " TLS verification options against upstream", function()
      describe("tls_verify", function()
        it("default is on, request is blocked", function()
          local proxy_client = get_proxy_client(subsystems, 19001)
          local path
          if subsystems == "http" then
            path = "/tls"
          else
            path = "/"
          end

          local res, err = proxy_client:send {
            path    = path,
            headers = {
              ["Host"] = "example.com",
            }
          }
          if subsystems == "http" then
            local body = assert.res_status(502, res)
            assert.matches("An invalid response was received from the upstream server", body)
          else
            assert.equals("connection reset by peer", err)
          end
          assert(proxy_client:close())
        end)

        -- buffered_proxying
        if subsystems == "http" then
          it("default is on, buffered_proxying = true, request is blocked", function()
            local proxy_client = get_proxy_client(subsystems, 19001)
            local path = "/tls-buffered-proxying"
            local res = proxy_client:send {
              path    = path,
              headers = {
                ["Host"] = "example.com",
              }
            }

            local body = assert.res_status(502, res)
            assert.matches("An invalid response was received from the upstream server", body)
            assert(proxy_client:close())
          end)
        end

        it("#db turn it off, request is allowed", function()
          local service_tls_id
          if subsystems == "http" then
            service_tls_id = service_tls.id
          else
            service_tls_id = tls_service_tls.id
          end
          local res = assert(admin_client:patch("/services/" .. service_tls_id, {
            body = {
              tls_verify = false,
            },
            headers = { ["Content-Type"] = "application/json" },
          }))

          assert.res_status(200, res)

          wait_for_all_config_update(subsystems)

          local path
          if subsystems == "http" then
            path = "/tls"
          else
            path = "/"
          end

          helpers.wait_until(function()
            local proxy_client = get_proxy_client(subsystems, 19001)
            res = proxy_client:send {
              path    = path,
              headers = {
                ["Host"] = "example.com",
              }
            }
            return pcall(function()
              local body = assert.res_status(200, res)
              assert.equals("it works", body)
              assert(proxy_client:close())
            end)
          end, 10)

          -- buffered_proxying
          if subsystems == "http" then
            helpers.wait_until(function()
              local proxy_client = get_proxy_client(subsystems, 19001)
              res = proxy_client:send {
                path    = "/tls-buffered-proxying",
                headers = {
                  ["Host"] = "example.com",
                }
              }

              return pcall(function()
                local body = assert.res_status(200, res)
                assert.equals("it works", body)
                assert(proxy_client:close())
              end)
            end, 10)
          end
        end)
      end)
    end)
    end
  end)
end
end   -- for flavor
