skip to Main Content

I use Ingress-nginx and I need to rewrite the Host header sent to the proxied Kubernetes Service based on the path of the original request. This must happen based on some variable or regular expression. I cannot hard code paths because this needs to work for any path automatically.

Basically I need something like this:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  namespace: my-ns
annotations:
    # below what's commented out almost works but adds a leading "/" to header
    # nginx.ingress.kubernetes.io/upstream-vhost: $request_uri.example.com
    nginx.ingress.kubernetes.io/upstream-vhost: $PATH.example.com
spec:
  ingressClassName: nginx
  rules:
    - host: 'example.com'
      http:
        paths:
        - backend:
            service:
              name: my-svc
              port:
                number: 80
          path: /$PATH
          pathType: Prefix

I wonder if this is somehow possible to implement with annotation nginx.ingress.kubernetes.io/configuration-snippet or nginx.ingress.kubernetes.io/server-snippet but I don’t understand these annotations well enough. I’ve tried out these annotations by passing in a location and also attempted to use map module with no luck so far.

Context: This reverse proxy is set up in front of Knative and Knative’s routing works through Host header.

2

Answers


  1. Chosen as BEST ANSWER

    Generally, setting the Host header depending on the path can be achieved like this:

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: my-ingress
      namespace: my-ns
      annotations:
        nginx.ingress.kubernetes.io/server-snippet: |
          location ~* "^/(?<myvar>.+)$" {
            proxy_set_header        Host $myvar.example.com;
            proxy_pass              http://my-svc.my-ns.svc.cluster.local;        
            proxy_http_version      1.1;
          }
    spec:
      rules:
        - host: "example.com"
          http:
            paths:
            - backend:
                service:
                  name: thisisignored
                  port:
                    number: 80
              path: /
              pathType: Prefix
    

    So we need nginx.ingress.kubernetes.io/server-snippet and also it must be added proxy_http_version 1.1;, otherwise it won't work.

    Note that apparently server-snippet overrules what is defined as service in the specs - the value of backend.service.name is ignored. So be careful when using server-snippet annotation as it may break the Ingress configuration is some way.


  2. without any snippet annotation

    Then… considering NGINX Ingress annotation snippets contain LUA code execution (which can be a security issue, by the way), you might consider using Lua through a custom Lua plugin.

    A simple Lua script would be enough to capture the first segment of the request path and sets it as the Host header suffixed with .example.com.

    Create a dynamic-host-header/main.lua: (inspired from the official example)

    local _M = {}
    
    function _M.rewrite()
        -- Check if this is the default backend, if so, skip processing.
        if ngx.var.proxy_upstream_name == "upstream-default-backend" then
            return
        end
    
        local path = ngx.var.uri
        local captured_path = string.match(path, "^/([^/]+)")
        if captured_path then
            ngx.req.set_header("Host", captured_path .. ".example.com")
        end
    end
    
    return _M
    

    Before building a custom ingress-nginx image, you can start with testing that plugin into the ingress-nginx pod:

    Create a ConfigMap with the content of your plugin:

    kubectl create configmap dynamic-host-header-plugin --from-file=dynamic-host-header/main.lua -n <NAMESPACE_WHERE_INGRESS_IS_RUNNING>
    

    And modify the ingress-nginx deployment to mount this ConfigMap to /etc/nginx/lua/plugins/dynamic-host-header (as mentioned in the "installing plugin" section):

    kubectl edit deployment -n <NAMESPACE_WHERE_INGRESS_IS_RUNNING> nginx-ingress-controller
    

    Add the volume and volumeMounts as follows:

    ...
    spec:
      containers:
      - name: nginx-ingress-controller
        ...
        volumeMounts:
        - mountPath: /etc/nginx/lua/plugins/dynamic-host-header
          name: dynamic-host-header
        ...
      volumes:
      - name: dynamic-host-header
        configMap:
          name: dynamic-host-header-plugin
    

    Now, you need to enable the plugin by updating the nginx-ingress-controller‘s ConfigMap:

    Edit the ConfigMap:

    kubectl edit configmap nginx-configuration -n <NAMESPACE_WHERE_INGRESS_IS_RUNNING>
    

    Add (or update) the plugins configuration setting:

    data:
      plugins: "dynamic-host-header"
    

    Finally, restart the ingress-nginx pods to pick up the changes:

    kubectl rollout restart deployment/nginx-ingress-controller -n <NAMESPACE_WHERE_INGRESS_IS_RUNNING>
    

    With this Lua plugin enabled, the Host header should be dynamically set based on the request path, as required.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search