supabase/supabase

Why anyone can access my supabase studio without login? (docker)

samerbahri98 opened this issue ยท 19 comments

Feature request

Is your feature request related to a problem? Please describe.

After running supabase from the docker-compose file, it runs perfectly on port 3000. However

  1. literally anyone who has access to my network can have access to the dashboard without login and can modify anything
  2. I cannot create or manage multiple projects with supabase studio

Describe the solution you'd like

  1. Add a login screen and an initial register sequence to supabase studio (something like what strapi or wordpress are doing)
  2. Add the ability to create and manage multiple projects (something like what budibase are doing in their dashboard)
    basically look at what budibase did and try to replicate it.

Describe alternatives you've considered

A temporary fix would be to require the user to have a token in the localstorage of the browser and add instructions for the user to install it

Additional context

This was posted here first in discussion #4226 back in December 2nd and it's still unaddressed
It's still an issue since the first time I tried supabase studio in November (I didn't think about it back them because exams at college)

Hey @samerbahri98 - the docker+studio is "BYO security". There is no "management database" for the the Dashbaord for us to add auth.

I have added this note to the docs here - https://supabase.com/docs/guides/hosting/docker#securing-the-dashboard

We will look at adding something very basic, but ultimately you should stlll implement some sort of VPN/other auth since this is your Database (eg: https://news.ycombinator.com/item?id=29761728)

If you want to help document some best security practices for Studio let me know - we'd love any contributions to the docs.

Yeah that pretty much answers my question. I'll see if I can work out something and share it with everyone in the future but that's not a promise.

As of currently I'll just use zerotier as a vpn.

Thank you for taking the time to answer my question :)

Just stumbling upon this, I've had the same issue and solved this by combining Supabase with Traefik and Authelia as a traefik middleware. In case anyone else ever has this problem.

Alternatively, as long as you use Traefik, you always have the option of creating your own auth middleware and then just using Traefik's ForwardAuth.

@karouvirae did you use this docker-compose file? -> https://github.com/supabase/supabase/blob/master/docker/docker-compose.yml

Would you please share your docker-compose with traefik?

@delightfuldude My setup is a little complicated right now. While the docker-compose.yml you pointed to is the one I'm using, I haven't modified that one much. Instead I run numerous docker-compose.yml side-by-side with a shared network between them. I will try to find the time to make a somewhat simplified example later today.

But until then, in essence all you really need to do is to setup the traefik docker-compose (https://doc.traefik.io/traefik/) with letsencrypt and, in the simplest case, use the basic-auth middleware for your supabase-studio route.

Small example with "user"/"password" as the username and password.
In the labels of the traefik service I define the following middleware:

       - "traefik.http.middlewares.traefik-auth.basicauth.users=user:$$apr1$$VQ8j8NG6$$DgbMArB.eVCeoxryNBs3Z."

Think of it as creating a middleware named "traefik-auth" (can be anything) utilizing the "basicauth" method (https://doc.traefik.io/traefik/v2.0/middlewares/basicauth/)

Then, in my supabase-studio labels all I need to do is this

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.supabase_studio_https.rule=Host(`${STUDIO_DNS}`)"
      - "traefik.http.routers.supabase_studio_https.entrypoints=websecure"
      - "traefik.http.routers.supabase_studio_https.tls=true"
      - "traefik.http.routers.supabase_studio_https.tls.certResolver=letsencrypt"
      - "traefik.http.routers.supabase_studio_https.tls.domains[0].main=${STUDIO_DNS}"
      - "traefik.http.routers.supabase_studio_https.service=studio-supabase-studio"
      - 'traefik.http.routers.supabase_studio_https.middlewares=traefik-auth'
      - "traefik.http.routers.supabase_studio_http.entrypoints=web"
      - "traefik.http.routers.supabase_studio_http.middlewares=https_redirect"
      - "traefik.http.middlewares.https_redirect.redirectscheme.scheme=https"
      - "traefik.http.middlewares.https_redirect.redirectscheme.permanent=true"
      - "traefik.http.services.studio-supabase-studio.loadbalancer.server.port=3000"
      - "traefik.docker.network=web"

${STUDIO_DNS} is just the domain where I host supabase-studio (e.g. studio.mydomain.net).
You may have to skip all of the https-stuff in its entirety if you're trying this out locally, because letsencrypt won't work that way.

traefik.http.routers.supabase_studio_https.service is set to studio-supabase-studio because the service in docker-compose is called "studio" and the container name is "supabase-studio"; join with "-" and "studio-supabase-studio" is what you get. Just pointing out to make sure.

And of course traefik.http.routers.supabase_studio_https.middlewares points to the authentication middleware that we have created previously: traefik-auth

As I said, I'll try to send over a singular example docker-compose.yml later today.

@delightfuldude Long work day. This should work:

#
# Edit the value of the traefik.http.middlewares.traefik-auth.basicauth.users label in the traefik service as necessary.
# The default is "user" as the username and "password" as the password.
#
# Add (and edit accordingly) the following into your .env:
#  STUDIO_DNS=studio.localdomain
#  TRAEFIK_DNS=traefik.localdomain
#  API_DNS=supabase.localdomain
#
# Make sure those domains point to the docker host and the host allows traefiks exposed port 80 to be reachable through it.
# This can be easily done by editing (on linux) /etc/hosts or (on windows) the C:\Windows\System32\drivers\etc\hosts file.
#
# You may remove PUBLIC_REST_URL from .env because it is no longer used under this setup.
#
version: '3.0'

services:
  traefik:
    image: traefik:v2.5
    restart: always
    labels:
       - "traefik.enable=true"

       - "traefik.http.routers.traefik_http.rule=Host(`${TRAEFIK_DNS}`)"
       - "traefik.http.routers.traefik_http.entrypoints=web"
       - "traefik.http.routers.traefik_http.service=api@internal"
       - "traefik.http.routers.traefik_http.middlewares=traefik-auth"

       - "traefik.http.middlewares.traefik-auth.basicauth.users=user:$$apr1$$46zj6hBn$$O9UmXSrzqzmgd74ID1wcM0"
    command:
      - "--api=true"
      - "--api.dashboard=true"
#      - "--log.level=DEBUG"
      - "--providers.docker"
      - "--providers.docker.network=example_web"
      - "--providers.docker.exposedByDefault=false"
      - "--entrypoints.web.address=:30000"
    ports:
      - "80:30000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - example_web

  studio:
    container_name: supabase-studio
    image: supabase/studio:latest
    restart: unless-stopped
    labels:
      - "traefik.enable=true"

      - "traefik.http.routers.supabase_studio_http.rule=Host(`${STUDIO_DNS}`)"
      - "traefik.http.routers.supabase_studio_http.entrypoints=web"
      - "traefik.http.routers.supabase_studio_http.service=studio-supabase-studio"
      - 'traefik.http.routers.supabase_studio_http.middlewares=traefik-auth'

      - "traefik.http.services.studio-supabase-studio.loadbalancer.server.port=3000"

      - "traefik.docker.network=example_web"
    networks:
      - example_web
      - supabase
    environment:
      STUDIO_PG_META_URL: http://meta:8080
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

      SUPABASE_URL: http://kong:8000
      SUPABASE_REST_URL: http://${API_DNS}/rest/v1/
      SUPABASE_ANON_KEY: ${ANON_KEY}
      SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}

  kong:
    container_name: supabase-kong
    image: kong:2.1
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.supabase_kong_http.rule=Host(`${API_DNS}`)"
      - "traefik.http.routers.supabase_kong_http.entrypoints=web"
      - "traefik.http.routers.supabase_kong_http.service=kong-supabase-kong"

      - "traefik.http.services.kong-supabase-kong.loadbalancer.server.port=8000"

      - "traefik.docker.network=example_web"
    networks:
      - example_web
      - supabase
    environment:
      KONG_DATABASE: "off"
      KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
      KONG_DNS_ORDER: LAST,A,CNAME
      KONG_PLUGINS: request-transformer,cors,key-auth,acl
    volumes:
      - ./volumes/api/kong.yml:/var/lib/kong/kong.yml

  auth:
    container_name: supabase-auth
    image: supabase/gotrue:v2.5.21
    depends_on:
      - db
    networks:
      - example_web
      - supabase
    restart: unless-stopped
    environment:
      GOTRUE_API_HOST: 0.0.0.0
      GOTRUE_API_PORT: 9999

      GOTRUE_DB_DRIVER: postgres
      GOTRUE_DB_DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres?search_path=auth

      GOTRUE_SITE_URL: ${SITE_URL}
      GOTRUE_URI_ALLOW_LIST: ${ADDITIONAL_REDIRECT_URLS}
      GOTRUE_DISABLE_SIGNUP: ${DISABLE_SIGNUP}

      GOTRUE_JWT_SECRET: ${JWT_SECRET}
      GOTRUE_JWT_EXP: ${JWT_EXPIRY}
      GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated

      GOTRUE_EXTERNAL_EMAIL_ENABLED: ${ENABLE_EMAIL_SIGNUP}
      GOTRUE_MAILER_AUTOCONFIRM: ${ENABLE_EMAIL_AUTOCONFIRM}
      GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL}
      GOTRUE_SMTP_HOST: ${SMTP_HOST}
      GOTRUE_SMTP_PORT: ${SMTP_PORT}
      GOTRUE_SMTP_USER: ${SMTP_USER}
      GOTRUE_SMTP_PASS: ${SMTP_PASS}
      GOTRUE_SMTP_SENDER_NAME: ${SMTP_SENDER_NAME}
      GOTRUE_MAILER_URLPATHS_INVITE: http://${API_DNS}/auth/v1/verify
      GOTRUE_MAILER_URLPATHS_CONFIRMATION: http://${API_DNS}/auth/v1/verify
      GOTRUE_MAILER_URLPATHS_RECOVERY: http://${API_DNS}/auth/v1/verify
      GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: http://${API_DNS}/auth/v1/verify

      GOTRUE_EXTERNAL_PHONE_ENABLED: ${ENABLE_PHONE_SIGNUP}
      GOTRUE_SMS_AUTOCONFIRM: ${ENABLE_PHONE_AUTOCONFIRM}

  rest:
    container_name: supabase-rest
    image: postgrest/postgrest:v9.0.0
    depends_on:
      - db
    networks:
      - supabase
    restart: unless-stopped
    environment:
      PGRST_DB_URI: postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres
      PGRST_DB_SCHEMAS: public,storage
      PGRST_DB_ANON_ROLE: anon
      PGRST_JWT_SECRET: ${JWT_SECRET}
      PGRST_DB_USE_LEGACY_GUCS: "false"

  realtime:
    container_name: supabase-realtime
    image: supabase/realtime:v0.21.0
    depends_on:
      - db
    networks:
      - supabase
    restart: unless-stopped
    environment:
      DB_HOST: db
      DB_PORT: 5432
      DB_NAME: postgres
      DB_USER: postgres
      DB_PASSWORD: ${POSTGRES_PASSWORD}
      DB_SSL: "false"
      PORT: 4000
      JWT_SECRET: ${JWT_SECRET}
      REPLICATION_MODE: RLS
      REPLICATION_POLL_INTERVAL: 100
      SECURE_CHANNELS: "true"
      SLOT_NAME: supabase_realtime_rls
      TEMPORARY_SLOT: "true"
    command: >
      bash -c "./prod/rel/realtime/bin/realtime eval Realtime.Release.migrate
      && ./prod/rel/realtime/bin/realtime start"

  storage:
    container_name: supabase-storage
    image: supabase/storage-api:v0.10.0
    depends_on:
      - db
      - rest
    networks:
      - supabase
    restart: unless-stopped
    environment:
      ANON_KEY: ${ANON_KEY}
      SERVICE_KEY: ${SERVICE_ROLE_KEY}
      POSTGREST_URL: http://rest:3000
      PGRST_JWT_SECRET: ${JWT_SECRET}
      DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres
      PGOPTIONS: -c search_path=storage,public
      FILE_SIZE_LIMIT: 52428800
      STORAGE_BACKEND: file
      FILE_STORAGE_BACKEND_PATH: /var/lib/storage
      TENANT_ID: stub
      # TODO: https://github.com/supabase/storage-api/issues/55
      REGION: stub
      GLOBAL_S3_BUCKET: stub
    volumes:
      - ./volumes/storage:/var/lib/storage

  meta:
    container_name: supabase-meta
    image: supabase/postgres-meta:v0.29.0
    depends_on:
      - db
    networks:
      - supabase
    restart: unless-stopped
    environment:
      PG_META_PORT: 8080
      PG_META_DB_HOST: db
      PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD}

  db:
    container_name: supabase-db
    image: supabase/postgres:14.1.0
    command: postgres -c config_file=/etc/postgresql/postgresql.conf
    restart: unless-stopped
    ports:
      - ${POSTGRES_PORT}:5432
    networks:
      - supabase
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./volumes/db/data:/var/lib/postgresql/data
      - ./volumes/db/init:/docker-entrypoint-initdb.d

networks:
  example_web:
    external: false
  supabase:
    external: false  

Carefully read and follow the instructions at the top of the compose file.
Feel free to ask if you have any further questions.

  • Do note that, at least on my computer, studio needed a bit of time to spool up properly before all the menus (like API) became fully available. When in doubt, give it 5 minutes after startup and refresh the page if it's already open.
  • You will still need to put the supabase /docker/volumes folder in the same directory as your docker-compose file.
  • This docker-compose is set-up to only serve http, please refer to traefiks documentation if you need https - it's quite easy!

One more thing: I ended up having to set-up an SMTP-server so that I could invite users from supabase studio. Inviting a user will error if it fails to send an email. I'm not currently aware of any easy workaround for this, as I haven't dived into gotrue too much yet (maybe there's an environment-variable that could bypass the necessity for a mailserver?). It is always an option, however, to execute some SQL on the postgre container and to add the user yourself.

@karouvirae thank you very much!! I will try it and give you a feedback!

@karouvirae I'm a bit confused on what to enter in regards to this:

#  STUDIO_DNS=studio.localdomain
#  TRAEFIK_DNS=traefik.localdomain
#  API_DNS=supabase.localdomain

Am I supposed to setup /etc/hosts and have these domains point to the local ip address of my docker container?
A bit stuck. Some help would be awesome.

@ryanhaticus In a development setup, using /etc/hosts on the corresponding client machine would suffice.
You wouldn't point those domain names at any particular container, but at the host that's running the compose file.
Traefik will then dispatch the requests to their intended containers.

If you're trying to go for a live-environment, you'll need to configure the domains accordingly.
I can't quite double check if I'm forgetting about anything important right now, but basically:

  1. Obtain a domain (we'll call it my-supabase.tld)
  2. Point *.my-supabase.tld (so all subdomains of this domain) to the host that the containers are running on
  3. Use studio.my-supabase.tld for STUDIO_DNS, traefik.my-supabase.tld for TRAEFIK_DNS, and apply the same pattern to API_DNS
  4. That should already be all.

@karouvirae I used your template and also enabled ssl. When I call studio, I get a mixed content error when loading the rest/v1/ routes. do you have any ideas?

@adrian-goe Have you modified the studio-services SUPABASE_REST_URL to use https too?

This URL is used by supabase studios javascript. So if you're serving studio itself via https, but haven't changed this environment variable to use https, that'll cause a mixed content error.

@karouvirae thanks, i missed that. now im getting a 502 ๐Ÿ˜… but at least a step further. thanks ๐Ÿ˜Š

@adrian-goe I suppose it's down to docker-compose logs now ๐Ÿ˜€
In case you can't find out why it's not working, if you send over your .env and docker-compose.yml I can take another look in the evening sometime.

@karouvirae thanks for the offer ๐Ÿ˜Š But who thinks that a postgres password should not contain an @?

And since supabase-rest could not access the database, the requests are rejected with 502.
Now everything works. Thanks for your template ๐Ÿ˜Š

I tried @karouvirae docker-compose.yml file and that did not work, I received errors from auth, rest, realtime, and storage

I tried both the following repositories and none of them worked.
https://github.com/supabase-community/supabase-traefik
https://github.com/supabase/supabase/tree/master/docker

First off, the unofficial Traefik setup that is supported by the community is outdated.
The main self-hosting guide containers all work, although the Dashboard gets stuck, and I need to get this running as soon as possible.

I even found the following issue about it:
#8721

Does this mean that self-hosting is currently not possible with the bug introduced in the above issue?
I have made a help post about this in the Supabase Official discord, and so far no one has responded.

In the r/Supabase Reddit, there is a similar post.
https://www.reddit.com/r/Supabase/comments/ycwude/comment/iuwa3az/?utm_source=share&utm_medium=web2x&context=3

I tried changing the following .env variables but no luck.

SITE_URL
API_EXTERNAL_URL
PUBLIC_REST_URL

Updates have been given in the mentioned issue below, this currently has not been resolved.

I think when deploying, there's no need to enable the studio service on the server. Instead, the studio service should be compiled into a local desktop application to ensure security. I'm trying to do this.

You can use caddy basicauth directive before forwarding requests. There is a YouTube video introducing how to use caddy to secure your supabase studio.

In my case. I just add basicauth directive before the reverse_proxy directive. After that, when I access my supabase studio (like supabase.example.com), I need to input the correct username and password and then access to my supabase studio.

supabase.example.com {
    basicauth {
        username passwd
    }
    reverse_proxy localhost:3000
}

caddy basicauth

When installing the open-source version, it should inherently include these features. Attempting to implement such functionalities independently challenges the true essence of open-source, as secure production deployment relies heavily on the original developers' expertise

And this should not be closed until multiple projects inside, with domain ssl support of your domain.