caddyserver / ingress

WIP Caddy 2 ingress controller for Kubernetes
Apache License 2.0
620 stars 70 forks source link

Strip/rewrite a path? #202

Closed RonquilloAeon closed 5 months ago

RonquilloAeon commented 5 months ago

Hello! I am wondering if there's a way to strip/rewrite a path? I have the following ingress (Terraform). I need to strip /gqldocs. Unfortunately, I haven't been able to get the strip to work. I'm not sure if I'm setting the annotation correctly. Please help.

resource "kubernetes_ingress_v1" "staging_rewrite_ingress" {
  metadata {
    name = "rewrite-path-ingress"
    namespace = "staging"
    annotations = {
      "caddy.ingress.kubernetes.io/rewrite-strip-prefix" = "true"
    }
  }

  spec {
    ingress_class_name = "caddy"

    rule {
      host = "example.com"
      http {
        path {
          backend {
            service {
              name = "apollo-docs-new-svc"
              port {
                number = 80
              }
            }
          }
          path = "/gqldocs"
          path_type = "Prefix"
        }
      }
    }
  }
}

Here's the K8s equivalent:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    caddy.ingress.kubernetes.io/rewrite-strip-prefix: "true"
  name: rewrite-path-ingress
  namespace: staging
spec:
  ingressClassName: caddy
  rules:
  - host: example.com
    http:
      paths:
      - backend:
          service:
            name: apollo-docs-new-svc
            port:
              number: 80
        path: /gqldocs
        pathType: Prefix
Xinayder commented 5 months ago

The syntax for this annotation is: caddy.ingress.kubernetes.io/rewrite-strip-prefix: "/path/to/strip".

So, if you want to strip /gqldocs from your path, your annotation should look like this:

caddy.ingress.kubernetes.io/rewrite-strip-prefix: /gqldocs

It will create a JSON config with the following structure:

{
  "handle": [
    {
      "handler": "rewrite",
      "strip_path_prefix": "/gqldocs"
    }
  ]
}

And, according to the documentation, it will strip the specified prefix from the beginning of the URL path, and it should be unescaped. If it has any escape characters in it, the path should have the escape characters at the exact same location for the matcher to work correctly.

https://caddyserver.com/docs/json/apps/http/servers/errors/routes/handle/rewrite/strip_path_prefix/ https://caddyserver.com/docs/json/apps/http/servers/errors/routes/handle/rewrite/

RonquilloAeon commented 5 months ago

The syntax for this annotation is: caddy.ingress.kubernetes.io/rewrite-strip-prefix: "/path/to/strip".

So, if you want to strip /gqldocs from your path, your annotation should look like this:

caddy.ingress.kubernetes.io/rewrite-strip-prefix: /gqldocs

It will create a JSON config with the following structure:

{
  "handle": [
    {
      "handler": "rewrite",
      "strip_path_prefix": "/gqldocs"
    }
  ]
}

And, according to the documentation, it will strip the specified prefix from the beginning of the URL path, and it should be unescaped. If it has any escape characters in it, the path should have the escape characters at the exact same location for the matcher to work correctly.

https://caddyserver.com/docs/json/apps/http/servers/errors/routes/handle/rewrite/strip_path_prefix/ https://caddyserver.com/docs/json/apps/http/servers/errors/routes/handle/rewrite/

Thank you for pointing me in the right direction! If I want to strip multiple paths (one per K8s service), can I add multiple annotations to the same ingress or do I need to create one ingress per service that needs to have its path rewritten?

Xinayder commented 5 months ago

I'm not sure that's possible given my limited knowledge of k8s (I'm still a beginner), but annotations are applied to a resource and can't be applied to a specific path, for example. What it means is that you cannot apply an annotation to a path, but only the full ingress resource.

It could be possible to workaround this by adding a new type of annotation on the ingress controller that allows you to specify which path to match the strip_path_prefix, perhaps something like:

caddy.ingress.kubernetes.io/rewrite-strip-prefix:
   - /path1: /prefix1
   - /path2: /prefix2

although I'm not sure if array-type annotations exist in k8s. On the Caddy side I think it's possible, since you can specify a matcher to the rewrite directive in Caddyfile.

RonquilloAeon commented 5 months ago

@Xinayder - creating one ingress class per path that needs to be rewritten worked. I don't know if there are any ramifiications from having multiple ingresses per namespace. So far, this configuration is working fine. I'm pasting the sample below for reference. Thank you a bunch for your help! I'm also learning about K8s.

resource "kubernetes_ingress_v1" "staging_ingress" {
  metadata {
    name = "services-ingress"
    namespace = "staging"
  }

  spec {
    ingress_class_name = "caddy"

    rule {
      host = example.com
      http {
        path {
          backend {
            service {
              name = "apollo-gateway-new-svc"
              port {
                number = 80
              }
            }
          }
          path = "/graphql"
          path_type = "Prefix"
        }
      }
    }
    rule {
      host = example.com
      http {
        path {
          backend {
            service {
              name = "api-new-svc"
              port {
                number = 80
              }
            }
          }
          path = "/admin"
          path_type = "Prefix"
        }
      }
    }

    rule {
      host = example.com
      http {
        path {
          backend {
            service {
              name = "api-new-svc"
              port {
                number = 80
              }
            }
          }
          path = "/api"
          path_type = "Prefix"
        }
      }
    }
  }
}

resource "kubernetes_ingress_v1" "staging_docs_ingress" {
  metadata {
    name = "docs-ingress"
    namespace = "staging"
    annotations = {
      "caddy.ingress.kubernetes.io/rewrite-strip-prefix" = "/gqldocs"
    }
  }

  spec {
    ingress_class_name = "caddy"

    rule {
      host = example.com
      http {
        path {
          backend {
            service {
              name = "apollo-docs-new-svc"
              port {
                number = 80
              }
            }
          }
          path = "/gqldocs"
          path_type = "Prefix"
        }
      }
    }
  }
}

resource "kubernetes_ingress_v1" "staging_metabase_ingress" {
  metadata {
    name = "metabase-ingress"
    namespace = "staging"
    annotations = {
      "caddy.ingress.kubernetes.io/rewrite-strip-prefix" = "/reporting"
    }
  }

  spec {
    ingress_class_name = "caddy"

    rule {
      host = example.com
      http {
        path {
          backend {
            service {
              name = "reporting-new-svc"
              port {
                number = 80
              }
            }
          }
          path = "/reporting"
          path_type = "Prefix"
        }
      }
    }
  }
}

I tried the following format w/ Terraform and it didn't work, unfortunately:

resource "kubernetes_ingress" "example" {
  metadata {
    name = "example"
    annotations = {
      "caddy.ingress.kubernetes.io/rewrite-strip-prefix" = "/path1 /prefix1\n/path2 /prefix2"
    }
  }

  spec {
    // Your ingress spec here
  }
}