11

I'm using a postgresql server and I want to forbid my users to see what other databases are on the same server.

Essentially a \l should only list his own database.

I'm pretty sure that there is a right which I need to revoke from the user but I can't find it in the docs.

2
  • 1
    Why not just have multiple instances running at once? Commented Feb 7, 2011 at 1:36
  • I need to run them all on the same port. Commented Feb 7, 2011 at 10:40

4 Answers 4

7

This seems to work but might have unforeseen consequences. It requires tinkering with system catalogues, which isn't really a good idea!

First off, you have to permit superusers to update system catalogues by adding this to your postgresql config:

allow_system_table_mods = on

and restart.

Now, you can use DDL statements to modify system catalogues (you should be afraid). Connect to one of the user databases (a test one would be a good idea) and:

alter table pg_catalog.pg_database rename to pg_database_catalog;
create view pg_catalog.pg_database as
  select oid, 1262::oid as tableoid, pg_database_catalog.*
  from pg_catalog.pg_database_catalog
  where has_database_privilege(pg_database_catalog.oid, 'connect');    
grant select on pg_catalog.pg_database to public;

You should now find that if you connect to that database as a low-priv user, the \l command will just list the databases that that user can connect to.

The problem is you now need to guess which database the users connect to initially to fetch their database list from. If they connect to their own database initially, then you're probably done at this point. If they connect to postgres or template1 first, then you need to make this change on that database instead.

It seems to me that this should work, since the pg_database catalog is referred to by postgres backends directly by oid, rather than by name, so moving it out of the way and changing which rows are shown in it should be invisible to them. In particular, you can't stop the server distinguishing to the user between a database not existing and them not having connection privilege.

I'm not going to make any promises that this sort of change doesn't screw something else up down the line. If it breaks, you get to keep the pieces.

You probably want to make this change in a template database, and create user databases from that in future, and deactivate the allow_system_table_mods setting when you're done (which requires a server restart, remember).

Also, I tested this on 9.0: it seems to me it should work on some earlier versions too, caveat emptor.

Sign up to request clarification or add additional context in comments.

5 Comments

Just tested this. Looks like it is working. Thank you very very much.
Ok, it broke something :) pg_dump and pg_dumpall are not working anymore. The Error is "Error message from server: ERROR: column "tableoid" does not exist" The command was: SELECT tableoid, oid, (SELECT rolname FROM pg_catalog.pg_roles WHERE oid = datdba) AS dba, pg_encoding_to_char(encoding) AS encoding, datcollate, datctype, datfrozenxid, (SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, shobj_description(oid, 'pg_database') AS description FROM pg_database WHERE datname = 'test_1'
Would be really awesome if you could help me to fix this, because other than this, you solution works just great.
Sounds like some system columns are needed that are missing from the view. It should be possible to work around this by re-creating the view using a command like create view pg_catalog.pg_database as select oid, 1262::oid as tableoid, * from .... to put in the additional columns.
great! I edited your answer in a way which works for me. You should look over it, but as I said, that way it works for me.
1

There's no such setting in pgsql. There are settings to prevent users from connecting to databases that they shouldn't (grant / revoke connect). Being able to see there's a database is no big deal. Being able to connect / have edit rights etc. is.

4 Comments

Well, it is a bad thing if you have people running frontend software on these databases which, while configuring, gets a list of all available databases and prompts the user for ever database he doesn't have the rights to access. Problem is that I can't change that behavior and have to deal with it in some way.
Depending on the context beeing able to see other databases is a big deal. It is a data leak. I don't see why a user should ever see a database he has no business in....
A declaration does not necessarily make your argument correct. If the user cannot access the db then seeing it is not big deal. Data are not being leaked. Data are what you store in a db, not the name of the db. If what you need is for users to not know what other databases are on a system then you need set up a cluster for each user so they have their own db cluster to log into.
This seems a bit old but I really have to throw this in: A list of your databases could mean a list of which clients are working with you, and it could also be a list of "services" for every client out there... now, beign said this, I think we should be concerned about direct query's to pg_catalog as well since it can list every connected user using XXXX db...
1

I would imagine this might have negative repercussions for the user, such as not being able to connect to the database since the system does not have access to the sytem tables, not sure though. But as far as figuring out what table to revoke - this a good general way to see what the psql meta commands are doing:

To see what \l is doing you can also use the -E flag from the command line with psql.

~$ psql -E -c '\l'
********* QUERY **********
SELECT d.datname as "Name",
       pg_catalog.pg_get_userbyid(d.datdba) as "Owner",
       pg_catalog.pg_encoding_to_char(d.encoding) as "Encoding",
       d.datcollate as "Collation",
       d.datctype as "Ctype",
       pg_catalog.array_to_string(d.datacl, E'\n') AS "Access privileges"
FROM pg_catalog.pg_database d
ORDER BY 1;
**************************

So if the user does not have access to pg_database they will not be able to use the \l command.

Comments

0

@araqnid 's answer above seems to be the way to go except for one problem: select oid, 1262::oid as tableoid, pg_database_catalog.* will have the oid column defined twice in its results, once as expicitly given via select oid and once taken from pg_database_catalog.*. At least on Postgresql 12 create view pg_catalog.pg_database will complain that the column oid is being defined twice and will abort.

Thus the corrected code would be:

alter table pg_catalog.pg_database rename to pg_database_catalog;
create view pg_catalog.pg_database as
  select 1262::oid as tableoid, pg_database_catalog.*
  from pg_catalog.pg_database_catalog
  where has_database_privilege(pg_database_catalog.oid, 'connect');    
grant select on pg_catalog.pg_database to public;

Please refer to the original answer for all other information.

I'd be glad if somebody could confirm that my findings here are correct (or refute them).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.