Skip to content

Exposing a local service on the apx.develop.eoepca.org ingress

Tilo Wiklund edited this page Feb 20, 2025 · 3 revisions

Why and what?

When quickly prototyping or debugging a service intended to run behind an ingress, such as the APISIX ingress on the EOEPCA development cluster, it can be helpful to expose a server running on a local development machine. This allows a local process to receive and respond to real HTTP requests as actually generated by the ingress.

The method outlined here requires no special software on the local development machine, beyond a standard SSH client. In particular, it does not require anything like remote devcontainers.

Once set up, it makes it possible to have a local process such as, say, a python/uvicorn web server listening on port 8080 on your laptop respond to requests made to https://<BB>.apx.develop.eoepca.org/somepathname from any system.

SECURITY NOTE: (!!!) You are exposing a local process (on your laptop) on a public endpoint https://<BB>.apx.develop.eoepca.org/, so BEWARE (e.g. don't run highly unsafe code with high privileges and kill the tunnel when you are not using it)!

How?

The idea is to do the following:

  • Run a single Pod exposing a port (e.g. 8080), running only a, properly configured, OpenSSH server (listening on another port),
  • set up a Service for the above Pod, and
  • set up an ingress route exposing the Service as https://<BB>.apx.develop.eoepca.org/tmpservice.

With the above done, https://<BB>.apx.develop.eoepca.org/tmpservice will now forward to the Pod on the configured port, where currently nothing is running.

The final step is then to expose a local process, such as our Python/uvicorn server, on the Pod using a reverse SSH tunnel. This will tunnel any requests to the Pod port, through the SSH connection, and to our local process.

Step one: set up a pod

Modify and apply (e.g. kubectl ... apply -f pod.yaml) the following Pod resource where indicated

pod.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: tmpservice-pod
  ## NOTE: Make sure to put in the appropriate namespace
  namespace: resource-health
  ## NOTE: You can use whatever labels to identify the pod
  labels:
    app.kubernetes.io/component: "tmpservice"
spec:
  containers:
    - name: tmpservice-pod-dev
      image: "linuxserver/openssh-server"
      ports:
      - name: http
        containerPort: 8080 ## CHANGE ME IF YOU WANT
        protocol: TCP
      - name: ssh
        containerPort: 2222
        protocol: TCP
      env:
      - name: PUBLIC_KEY
        ## NOTE: CHANGE ME TO YOUR SSH PUBLIC KEY!
        value: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDjEj4U8/kIQ0w9VrekbPqjO4esbRTBYSvW8U2TrYYRG tilo.wiklund@sensmetry.com"
      - name: SUDO_ACCESS
        value: "true"
        ## NOTE: CHANGE ME TO WHATEVER USERNAME YOU WANT
      - name: USER_NAME
        value: tilo
      - name: DOCKER_MODS
        value: linuxserver/mods:openssh-server-ssh-tunnel

This creates a pod with a single user (specified by USER_NAME) that can be accessed through ssh using the private key corresponding to the configured PUBLIC_KEY. See for example here if you need help generating an ssh key.

Step two: expose on ingress

Create a service for the pod, for example

apiVersion: v1
kind: Service
metadata:
  ## NOTE: CHANGE ME!
  name: resource-health-tmpservice-service
  namespace: resource-health
  labels:
    app.kubernetes.io/component: "tmpservice"
spec:
  type: ClusterIP
  ports:
    - port: 8080
      targetPort: 8080
      protocol: TCP
      name: http
  ## NOTE: Make sure this matches the labels set in the pod metadata
  selector:
    app.kubernetes.io/component: "tmpservice"

Then expose this service on the ingress, along the lines of (see here if you have not set up an APISIX route yet):

apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  ...
spec:
  http:
  ...
  ...
  - name: resource-health-tmpservice-route
    match:
      hosts:
        - resource-health.apx.develop.eoepca.org
      paths:
        - /tmpservice
        - /tmpservice/*
    backends:
      - serviceName: resource-health-tmpservice-service
        servicePort: 8080
    plugins:
      - name: proxy-rewrite
        enable: true
        config:
          regex_uri: ["^/tmpservice/?(.*)","/$1"]
      - ...
    plugin_config_name: ...

Step three: Create a reverse tunnel

In order to establish the SSH tunnel, we must first create a local port-forward to reach the ssh server in the Pod, and then create a tunnel through this forwarded connection.

To forward the SSH server with kubectl run along the lines of

kubectl --namespace=resource-health port-forward tmpservice-pod 2222:2222

with --namespace=resource-health and tmpservice-pod replaced as appropriate.

Finally, in order to set up a tunnel that accept connections from anywhere (0.0.0.0) on the remote port (8080) to any process listening locally (on 8080), run

ssh -R 0.0.0.0:8080:localhost:8080 <USER_NAME SET EARLIER>@localhost -p 2222

You may have to specify the additional argument -i path/to/private/key.

This should drop you into a remote session like

Welcome to OpenSSH Server
tmpservice-pod:~$

which you can simply let sit (until you are done).

Step four: Test

You can test the setup by running something like

python3 -m http.server 8080 -d /some/safe/directory/

and then accessing the route you set up earlier (e.g. resource-health.apx.develop.eoepca.org/tmpservice).

Step five: Closing the tunnel

Once you exit the ssh session

Welcome to OpenSSH Server
tmpservice-pod:~$ exit
logout
Connection to localhost closed.
$ 

the tunnel should be closed.