Strip part of a path prefix when routing


#1

My company recently upgraded to Kong Enterprise 0.33; previously we were using Kong 0.12, so as part of the migration we switched to services and routes. We use path prefixes for routing, so each service is “namespaced” by a path prefix (e.g., /my-service), and each route for that service uses the prefix (e.g., /my-service/healthz).

Our backend APIs have no idea what that prefix is (running locally the path is just /healthz). However, I haven’t found a clean way to manage multiple routes with the same prefix. For example, I’d like /my-service/healthz to route to /healthz. Things I’ve tried:

  • use the request-transformer-advanced plugin and set replace.uri to /healthz (this is what I’m currently doing)
  • create a separate service with one route that proxies to the same backend (this is what we were doing previously with APIs)
  • create a plugin that replaces the a configured prefix (I’ve started to do that here: https://github.com/alexashley/kong-plugin-path-prefix)

Is there a better way to accomplish this?


Strip part of URI
#2

For me, this raises questions about what the recommended practice is for breaking up path components between the Kong Services and their Routes. For example, if I have routes in my API which all begin with a version like example.com/v2/contacts should I put the /v2/ bit of the path in the Service? Why or why not?


#3

Hello @alexashley,

I might be understanding you wrong but could you not simply create a service and then a route under the service as follows?

Service:

{
    "connect_timeout": 60000,
    "created_at": 1538032915,
    "host": "my-service.internal",
    "id": "cd3ca8a5-4c26-4af4-8f75-8714ecde97e0",
    "name": null,
    "path": "/",
    "port": 80,
    "protocol": "http",
    "read_timeout": 60000,
    "retries": 5,
    "updated_at": 1538032915,
    "write_timeout": 60000
}

and a route as:

{
    "created_at": 1538033038,
    "hosts": null,
    "id": "e37d467f-7923-4393-b027-9d1c5e4f54f8",
    "methods": null,
    "paths": [
        "/my-service"
    ],
    "preserve_host": false,
    "protocols": [
        "http",
        "https"
    ],
    "regex_priority": 0,
    "service": {
        "id": "e37d467f-7923-4393-b027-9d1c5e4f54f8"
    },
    "strip_path": true,
    "updated_at": 1538033038
}

Since strip_path is set as true, it will always strip /my-service from the Path.


#4

Yes, I believe strip_path would work for the a route like /my-service, but I’m a little confused on the best way to tie multiple routes to a single service. A root path like /my-service with strip_path set to true results in an upstream request with a path of /, and isn’t the same true for a path like /my-service/foobar? In that second case, I’d like the proxied path to instead be /foobar.

I could create another service with the same upstream API and give it a path of /foobar, then attach a route with a path of /my-service/foobar (with strip_path set to true) and get the result I’m looking for; however, I’m wondering if it’s possible to do this with a single service.

The problem I’m trying to solve is that I have a single route that requires authentication and several other routes that do not; these routes all proxy to the same deployed API, so it made sense to me to configure Kong with a single service but perhaps that’s not the best way to accomplish that.


#5

No, that’s exactly the point of strip_path in Routes.

If your route has /my-service in the paths property, then
/my-service/foo will be routed upstream as /foo,
/my-service/bar will be routed upstream as /bar.

I’d recommend you to go through the definition of the Route Object in Kong to gain a better understanding.

If this answers your question, please mark it as “Solved” so that future community members can easily navigate the forum, thanks!


#6

@hbagdi Thanks for your help. I reviewed the documentation, but I don’t think I was clear earlier with my exact problem. Here’s a full example of what I’m trying to achieve:

Create a service backed by Mockbin:

curl -X POST ${KONG_API_URL}/services -d name=mockbin-service -d url=https://mockbin.org/request
{
  "host": "mockbin.org",
  "created_at": 1538023618,
  "connect_timeout": 60000,
  "id": "4adfa53a-b988-4ae7-820c-ce714d3760b0",
  "protocol": "https",
  "name": "mockbin-service",
  "read_timeout": 60000,
  "port": 443,
  "path": "/request",
  "updated_at": 1538023618,
  "retries": 5,
  "write_timeout": 60000
}

Create the first route for that service:

curl ${KONG_API_URL}/routes -d service.id=4adfa53a-b988-4ae7-820c-ce714d3760b0 -d paths[]=/mock
{
  "created_at": 1538023756,
  "strip_path": true,
  "preserve_host": false,
  "regex_priority": 0,
  "updated_at": 1538023756,
  "paths": [
    "/mock"
  ],
  "service": {
    "id": "4adfa53a-b988-4ae7-820c-ce714d3760b0"
  },
  "protocols": [
    "http",
    "https"
  ],
  "id": "51449f7d-6654-4e8c-a7e7-ce69b786d907"
}

Now I add basic-auth to the route, create a consumer, and credentials for the consumer:

curl -X POST ${KONG_API_URL}/plugins -d route_id=51449f7d-6654-4e8c-a7e7-ce69b786d907 -d name=basic-auth

{
  "created_at": 1538023951000,
  "config": {
    "hide_credentials": false,
    "anonymous": ""
  },
  "id": "156e4bc4-c6bb-4275-b632-f84433abd7c7",
  "name": "basic-auth",
  "enabled": true,
  "route_id": "51449f7d-6654-4e8c-a7e7-ce69b786d907"
}
curl -X POST ${KONG_API_URL}/consumers -d username=foo | jq
{
  "custom_id": null,
  "created_at": 1538024023,
  "username": "foo",
  "id": "3a65a9be-030c-494a-803f-5cd2d2092025"
}
curl -X POST ${KONG_API_URL}/consumers/3a65a9be-030c-494a-803f-5cd2d2092025/basic-auth -d password=bar -d username=foo
{
  "created_at": 1538024115000,
  "id": "c19c8994-ebd7-44ad-b970-cdc883901f47",
  "password": "f94b4787a8d6524811ebf32a7b755adaa4555531",
  "username": "foo",
  "consumer_id": "3a65a9be-030c-494a-803f-5cd2d2092025"
}

Now I can hit /mock authenticated and it proxies to Mockbin as expected:

curl ${KONG_PROXY}/mock -u foo:bar

{
  "startedDateTime": "2018-09-27T04:57:01.945Z",
  "clientIPAddress": "172.22.0.1",
  "method": "GET",
  "url": "http://localhost/request",
  "httpVersion": "HTTP/1.1",
  "cookies": {},
  "headers": {
    "host": "mockbin.org",
    "connection": "close",
    "x-forwarded-for": "172.22.0.1, 10.1.193.34, 54.177.235.50",
    "x-forwarded-proto": "http",
    "x-forwarded-host": "localhost",
    "x-forwarded-port": "80",
    "x-real-ip": "104.166.249.156",
    "kong-request-id": "6b1bd6b2fcb1ebaf4537ef2c6db2748d",
    "kong-client-id": "mockbin",
    "authorization": "Basic Zm9vOmJhcg==",
    "user-agent": "curl/7.47.0",
    "accept": "*/*",
    "x-request-id": "1453cdcf-8b7e-4e29-b194-3ea126d7f80f",
    "via": "1.1 vegur",
    "connect-time": "1",
    "x-request-start": "1538024221936",
    "total-route-time": "0"
  },
  "queryString": {},
  "postData": {
    "mimeType": "application/octet-stream",
    "text": "",
    "params": []
  },
  "headersSize": 527,
  "bodySize": 0
}

Now I’d like to expose an unauthenticated /version endpoint of my API, with a public path of /mock/version.

 curl -X POST ${KONG_API_URL}/routes -d service.id=4adfa53a-b988-4ae7-820c-ce714d3760b0 -d paths[]=/mock/version
 {
  "created_at": 1538024714,
  "strip_path": true,
  "preserve_host": false,
  "regex_priority": 0,
  "updated_at": 1538024714,
  "paths": [
    "/mock/version"
  ],
  "service": {
    "id": "4adfa53a-b988-4ae7-820c-ce714d3760b0"
  },
  "protocols": [
    "http",
    "https"
  ],
  "id": "5fe34ec3-9555-4838-b063-1a1e4c76f602"
}

This route has strip_path set to true, so it will actually proxy to the same upstream path as /mock:

curl -s ${KONG_PROXY}/mock/version | jq .url
"http://localhost/request"
curl -s ${KONG_PROXY}/mock -u foo:bar | jq .url
"http://localhost/request"

And if I set strip_path=false for the route it will proxy the entire matched route:

 curl -X PATCH ${KONG_API_URL}/routes/5fe34ec3-9555-4838-b063-1a1e4c76f602 -d strip_path=false
 curl -s ${KONG_PROXY}/mock/version | jq .url
"http://localhost/request/mock/version"

So my question is how to configure Kong to proxy that second route (/mock/version) as /version to the upstream?


#7

Hello @alexashley,

Thank you for explaining your case in detail. It seems like this is a special endpoint and request-transformer-advanced plugin seems to be the right solution to the problem.

I see in the original post that you’re only seeking a better way to accomplish this but there doesn’t exists one AFAIK.

If you feel you’ve the answer, please mark the post as “Solved” to help the community navigate the forum easily. Thank you!


#8

Yeah, this a bit of a special case, there are only two endpoints we’re exposing like this. Thanks for the help @hbagdi !


#9

@alexashley As it’s currently not easily achievable with Kong. I’ve requested a new feature, please vote/like it so we get Kong’s attention :wink: Strip part of URI