Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

swagger-ui: "Try it Now" functionality stripping path prefixes when run behind nginx-ingress reverse proxy in Kubernetes/K8S. #6838

Open
ghost opened this issue Jan 15, 2021 · 8 comments

Comments

@ghost
Copy link

ghost commented Jan 15, 2021

Q&A (please complete the following information)

  • OS: Linux (Ubuntu x86_64 LTS 20.04).
  • Browser: Firefox.
  • Version: v84.0.1 64-bit.
  • Method of installation: Helm v3.x in K8S (though trying the manual "dist/index.html" approach yields similar results).
  • Swagger-UI version: v3.24.3 (pre-built Docker container), v3.40.0 (plain old "dist/index.html" approach).
  • Swagger/OpenAPI version: OpenAPI 3.0.

Content & configuration

  • Setup an instance of an application exposed through an nginx-ingress rule in K8S. Sample ingress YAML included below. In this example, the host is roadrunner.acme.co, and the service is exposed on http://roadrunner.acme.com/meep/v1.
    • Note: I've found a few other projects on GitHub or via Google searches where the solution is "just expose your app on hostname/, rather than a custom path invoking nginx rewrites such as hostname/meep/v1.
    • This is not an option, as that would assume only a single application is exposed via the ingress controller. In a typical/production use case, each micro-service in behind the ingress-control would get its own dedicated base path, like meep/v1 in my example).
  • Download and extract the contents of the "plain old HTML/JS" archive.
  • Modify the dist/index.html file so the URL field points to the OpenAPI spec of my service (i.e. http://roadrunner.acme.co/meep/v1/openapi.json; the service is a simple Python Flash+Swagger example that auto-exposes the openapi.json endpoint).
  • I am able to access the Swagger-UI web UI example, interact with it, etc. However, if I attempt to use the "Try It Now" ==> "Execute" functionality, the API calls to the live/running app fail due to a "Network error".
  • Upon closer inspection, I see that the "Execute" calls are attempting to communicate with http://roadrunner.acme.co/v1/ rather than http://roadrunner.acme.co/meep/v1 (i.e. the re-written portion of the URL is absent).
  • I was able to arrive at the same result by deploying swagger-ui into the cluster directly via a third-party helm chart.

Example ingress:

---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: sample-ingress-meep-meep
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
    - http:
        paths:
          - backend:
              serviceName: velocitus-good-with-ketchup-ius
              servicePort: 80
            path: /meep/(v1/.*)

Example Swagger/OpenAPI definition: N/A.

Swagger-UI configuration options:

window.onload = function() {
      // Begin Swagger UI call region
      const ui = SwaggerUIBundle({
        url: "http://roadrunner.acme.co/meep/v1/openapi.json",
        dom_id: '#swagger-ui',
        deepLinking: true,
        presets: [
          SwaggerUIBundle.presets.apis,
          SwaggerUIStandalonePreset
        ],
        plugins: [
          SwaggerUIBundle.plugins.DownloadUrl
        ],
        layout: "StandaloneLayout"
      })
      // End Swagger UI call region

      window.ui = ui
    }

Describe the bug you're encountering

  • The "Try It Now" ==> "Execute" functionality does not work, but access to the HTML app in general works.

To reproduce...

Example described above.

Expected behavior

  • The "Try It Now" ==> "Execute" functionality uses the exact same URL (i.e. hostname plus base path) that is used to access the running OpenAPI-enabled service (maybe using X-Forwarded-For or additional parameters supplied to the ingress-controller, so that re-writes don't break functionality).

Additional context or thoughts

There's a hack I've figured out in order to make the "Try It Now" ==> "Execute" functionality work, but the downside (severe), is that I need to supply the external hostname to the swagger-ui app at chart installation time (i.e. when I install swagger-ui, it needs to be provided with the string http://roadrunner.acme.co/meep/v1 at installation time). This isn't a permanent/viable solution though, as various users may "see" a different hostname and base path when accessing this micro-service (e.g. if it's exposed via multiple domains with a common load balancer, or if some people attempt to access it via IP address rather than this specific host name). It also prevents 100% end-to-end automated installation, as someone needs to supply the external FQDN + base path at chart installation time.

Hack-y workaround:

  • Deploy an OpenAPI-enabled microservice, expose it via an ingress rule (below). Can be deployed via kubectl apply --namespace=acme-ns -f ingress-meep.yaml.

ingress-meep.yaml

---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: sample-ingress-meep-meep
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
    - http:
        paths:
          - backend:
              serviceName: velocitus-good-with-ketchup-ius
              servicePort: 80
            path: /meep/(v1/.*)
  • Pre-create an ingress rule for swagger-ui (below). Can be deployed via kubectl apply --namespace=acme-ns -f ingress-demo.yaml.

ingress-demo.yaml

---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: sample-ingress-swaggerui-demo
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
    nginx.ingress.kubernetes.io/enable-cors: "true"
spec:
  rules:
    - http:
        paths:
          - backend:
              serviceName: swagger-demo-swaggerui
              servicePort: 8080
            path: /swagger-demo/(.*)
  • Using the following commands to deploy the chart.
# Make chart "visible" to Helm.
helm repo add cetic https://cetic.github.io/helm-charts
helm repo update

# Delete old copy if present.
helm delete --namespace=acme-ns swagger-demo

# Install chart.
helm install --namespace=acme-ns swagger-demo cetic/swagger-ui \
    --set swaggerui.jsonUrl="http://velocitus-good-with-ketchup-ius.acme-ns.svc.cluster.local/v1/openapi.json" \
    --set swaggerui.server.url="http://roadrunner.acme.co/meep/v1/"
  • Navigate to <http://roadrunner.acme.co/swagger-demo/> in my browser. Application loads and is visible in my browser.
  • Change the "Servers" via the drop-down UI menu (top-left) from v1 to http://roadrunner.acme.co/meep/v1. Both the general UI features, and the live debug "Try It Now" features work as expected.

Possibly related issues (external projects)

@DanSibbernsen
Copy link

DanSibbernsen commented May 28, 2021

Hi! Not certain what version of Kubernetes you're using (I'm on Kubernetes 1.20.4 and NGINX Ingress controller v0.44.0),. Adding the nginx.ingress.kubernetes.io/x-forwarded-prefix annotation worked for me.

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: sample-ingress-swaggerui-demo
  annotations:
    nginx.ingress.kubernetes.io/x-forwarded-prefix: "/swagger-demo"
    nginx.ingress.kubernetes.io/rewrite-target: /$1
    nginx.ingress.kubernetes.io/enable-cors: "true"
spec:
  rules:
    - http:
        paths:
          - backend:
              serviceName: swagger-demo-swaggerui
              servicePort: 8080
            path: /swagger-demo/(.*)

Note: I'm using networking.k8s.io/v1

edit:: Documentation for nginx.ingress.kubernetes.io/x-forwarded-prefix

@80rian
Copy link

80rian commented Nov 22, 2021

@DanSibbernsen
What if you have multiple paths? You don't want to pass x-forwarded-prefix to other paths than swagger-demo-swaggerui.

@DanSibbernsen
Copy link

@80rian Unfortunately my solution doesn't cover that that (for the apps I maintain, we only have 1 path per service, and the only one I cared about for the x-forwarded-prefix was for Swagger). Per this comment, it seems you'd have to define multiple ingresses in order to have different x-forwarded-prefix values passed along.

@80rian
Copy link

80rian commented Nov 22, 2021

Gotcha. Thanks!

@happyincent
Copy link

happyincent commented Jan 28, 2022

Hi! I tried to pass different x-forwarded-prefix values to multiple backend paths on the ingress-nginx (v1.0.4) and Kubernetes (1.21.7) through the nginx.ingress.kubernetes.io/x-forwarded-prefix annotation as @DanSibbernsen's comment.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-demo
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$3
    nginx.ingress.kubernetes.io/x-forwarded-prefix: /$1
spec:
  rules:
  - http:
      paths:
      - path: /()()(.*)
        pathType: Prefix
        backend:
          service:
            name: web
            port:
              number: 80
      - path: /(XXXApi)(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: xxx-api
            port:
              number: 80
      - path: /(YYYApi)(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: yyy-api
            port:
              number: 80

@ov-petrov
Copy link

ov-petrov commented Jul 8, 2022

@happyincent Thank you very much!

@MrAliev
Copy link

MrAliev commented Sep 11, 2022

Someone help solve the problem!

Hello. I ran into a similar problem and google brought me to this thread.
I am using kubernetes v1.23.6 and nginx-ingress-controller v1.3.1

If I use host-based routing using the address "api.[SomeDomain].com" then everything works fine and I can access the api and SwaggerUI.
But when I try to use path-based Routing "api.[SomeDomain].com/test", then I constantly fail:
When going to the address "api.[SomeDomain].com/test/swagger/index.html" I get a SwaggerUI page with the error:
"API definition could not be loaded. The status of the response to the sampling error is 404 /swagger/v1/swagger.json".
But if I go from the browser to: "api.[SomeDomain].com/test/swagger/v1/swagger.json" - I get normal json in response. Api requests at the address "api.[SomeDomain].com/test/..." also pass a good one.
I tried many examples and the internet, but none solved my problem!
** Of course, instead of "[SomeDomain]" I use the real domain name, which is resolved through the global DNS :-)

The example suggested here fom @happyincent - also doesn't work for me.

Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: testapi-ingress
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$3
    nginx.ingress.kubernetes.io/x-forwarded-prefix: /$1
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - api.[SomeDomain].com
    secretName: "wildcard-tls-secret"
  rules:
  - host: api.[SomeDomain].com
    http:
      paths:
      - path: /(test)(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: testapi-svc
            port:
              number: 80

Error in SwaggerUi:
image

@goors
Copy link

goors commented May 13, 2023

same here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants