ShinyProxy Proxy API - How to hide *sensitive* information

Hello,

I am currently fooling around with the Proxy API and noticed that, when accessed through POST, all the app information is returned, including some very sensitive data like resource limits, mounted volume paths etc… For example:

curl -X POST http://localhost:8080/api/proxy/demo

will return:

{"id":"845348d4-5d02-423c-85db-4f8ffb30cd6b","spec":{"id":"demo","displayName":"DemoApp","description":null,"logoURL":null,"accessControl":{"groups":null,"users":null,"expression":null},"containerSpecs":[{"image":"r-base-image","cmd":["R","-e","shiny::runApp('/root/demo')"],"env":null,"envFile":null,"network":null,"networkConnections":null,"dns":null,"volumes":["/home/user/Desktop/Shiny-Apps/demo_extra_data:/root/demo_extra_data"],"portMapping":{"default":3838},"privileged":false,"memoryRequest":null,"memoryLimit":null,"cpuRequest":null,"cpuLimit":null,"targetPath":null,"labels":{},"settings":{}}],"runtimeSettingSpecs":null,"settings":{},"kubernetesAdditionalManifests":[],"kubernetesAdditionalPersistentManifests":[],"maxLifeTime":null,"heartbeatTimeout":null,"kubernetesPodPatch":null},"status":"Up","startupTimestamp":1652777191041,"createdTimestamp":1652777187101,"userId":"dCGEhUsGsZ10NDUtrnT014GMsZop6Lb-61OjxdQK","containers":[{"id":"888e55d65ec608e44f4382d998cd2a35144127b7998afd8572ef333812e944c1","spec":{"image":"r-base-image","cmd":["R","-e","shiny::runApp('/root/demo')"],"env":null,"envFile":null,"network":null,"networkConnections":null,"dns":null,"volumes":["/home/user/Desktop/Shiny-Apps/demo_extra_data:/root/demo_extra_data"],"portMapping":{"default":3838},"privileged":false,"memoryRequest":null,"memoryLimit":null,"cpuRequest":null,"cpuLimit":null,"targetPath":null,"labels":{},"settings":{}}}],"targets":{"845348d4-5d02-423c-85db-4f8ffb30cd6b":"http://localhost:20003"},"runtimeValues":{"SHINYPROXY_USERGROUPS":"ANONYMOUS","SHINYPROXY_USERNAME":"dCGEhUsGsZ10NDUtrnT014GMsZop6Lb-61OjxdQK","SHINYPROXY_SPEC_ID":"demo","SHINYPROXY_CREATED_TIMESTAMP":"1652777187101","SHINYPROXY_PROXIED_APP":"true","SHINYPROXY_INSTANCE":"80215885020d0e51660b64b3c1345429ce0c2d8d","SHINYPROXY_PROXY_ID":"845348d4-5d02-423c-85db-4f8ffb30cd6b"}} 

Most importantly, this is not limited to the host machine, but can also happen with public apps accessed remotely. For example, a request to http://mysite.com:8080/api/proxy/demo will return the information on the demo app hosted on the mysite.com host.

Obviously, this is a HUGE security risk; the app properties can be exploited to gain access to the host server or perform attacks.

From the documentation, I understand that the proxy API is controlled by the same authentication as shinyproxy in general; which means that, a publicly available app for general use (where no username/password authentification is set) is unprotected.

I also checked the Oauth example (https://github.com/openanalytics/shinyproxy-config-examples/tree/master/09-api-oauth2); this is not a solution, as it provides protection only for the port handled by the nodejs app; the original shinyproxy port will still be unprotected.

Is there a way to somehow mask that particular part of shinyproxy so that it is not publicly accessible?

Ok, I eventually figured out how to solve this myself. Just for posterity, I am listing my solution here, so that anyone facing the same issue might benefit from it.

  1. Configure your host machine so that the port assigned to shinyproxy is only accessible from the localhost. In Linux, you can do this with iptables and ip6tables for IPv4 and IPv6, respectively.

  2. Use apache2 (or nginx, or anything else) to write proxy settings and forward shinyproxy. Example settings are the following:

  RewriteEngine on
  RewriteCond %{HTTP:Upgrade} =websocket
  RewriteRule /(.*) ws://0.0.0.0:8080/$1 [P,L]
  RewriteCond %{HTTP:Upgrade} !=websocket
  RewriteRule /(.*) http://0.0.0.0:8080/$1 [P,L]
  ProxyRequests Off
  ProxyPreserveHost On
  ProxyPass / http://0.0.0.0:8080/
  ProxyPassReverse / http://0.0.0.0:8080/
  1. Add the following additional lines to block access to /api/proxy:
<ProxyMatch "/api/proxy/(.*)" >
    Order Deny,Allow
    Deny from all
</ProxyMatch>

Hi

Thanks for sharing your solutions. Indeed we use a similar config for such deployments.
Nevertheless, I think we can document this behavior better and also provide a built-in option to hide sensitive (or most) parts of the API. I created an internal issue for this.

Note that this might break some features of ShinyProxy. For example, the switch-instance, stop and restart buttons will not work properly. However, typically these features are not used in a public deployment.

1 Like