From bf489d003b691aa9c0c2568fc28f5d1434f073b4 Mon Sep 17 00:00:00 2001 From: Dominik Moritz Date: Tue, 9 Oct 2012 17:55:08 +0100 Subject: [PATCH] Change the set-up process as recommended by @kindly --- ckanext/datastore/bin/__init__.py | 0 ckanext/datastore/bin/datastore_setup.py | 79 ++++++++++++++ ckanext/datastore/bin/set_permissions.sql | 56 +++++----- ckanext/datastore/commands.py | 127 +++------------------- 4 files changed, 120 insertions(+), 142 deletions(-) create mode 100644 ckanext/datastore/bin/__init__.py create mode 100644 ckanext/datastore/bin/datastore_setup.py diff --git a/ckanext/datastore/bin/__init__.py b/ckanext/datastore/bin/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ckanext/datastore/bin/datastore_setup.py b/ckanext/datastore/bin/datastore_setup.py new file mode 100644 index 00000000000..46d015b61aa --- /dev/null +++ b/ckanext/datastore/bin/datastore_setup.py @@ -0,0 +1,79 @@ +''' +Setup the right permissions on the datastore db +''' + +import sys +import os +import logging + + +def _run_cmd(command_line, inputstring=''): + logging.info("Running:", command_line) + import subprocess + p = subprocess.Popen( + command_line, shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout_value, stderr_value = p.communicate(input=inputstring) + if stderr_value: + print '\nAn error occured: {0}'.format(stderr_value) + sys.exit(1) + + +def _run_sql(sql, as_sql_user, database='postgres'): + logging.debug("Executing: \n#####\n", sql, "\n####\nOn database:", database) + _run_cmd("sudo -u '{username}' psql --dbname='{database}' -W".format( + username=as_sql_user, + database=database + ), inputstring=sql) + + +def set_permissions(pguser, ckandb, datastoredb, ckanuser, writeuser, readonlyuser): + __dir__ = os.path.dirname(os.path.abspath(__file__)) + filepath = os.path.join(__dir__, 'set_permissions.sql') + with open(filepath) as f: + set_permissions_sql = f.read() + + sql = set_permissions_sql.format( + ckandb=ckandb, + datastoredb=datastoredb, + ckanuser=ckanuser, + writeuser=writeuser, + readonlyuser=readonlyuser) + + _run_sql(sql, + as_sql_user=pguser, + database=datastoredb) + + +if __name__ == '__main__': + import argparse + argparser = argparse.ArgumentParser( + description='Set the permissions on the CKAN datastore. ', + epilog='"The ships hung in the sky in much the same way that bricks don\'t."') + + argparser.add_argument('-p', '--pg_super_user', dest='pguser', default='postgres', type=str, + help="the postgres super user") + + argparser.add_argument(dest='ckandb', default='ckan', type=str, + help="the name of the ckan database") + argparser.add_argument(dest='datastoredb', default='datastore', type=str, + help="the name of the datastore database") + argparser.add_argument(dest='ckanuser', default='ckanuser', type=str, + help="username of the ckan postgres user") + argparser.add_argument(dest='writeuser', default='writeuser', type=str, + help="username of the datastore user that can write") + argparser.add_argument(dest='readonlyuser', default='readonlyuser', + help="username of the datastore user who has only read permissions") + + args = argparser.parse_args() + + set_permissions( + pguser=args.pguser, + ckandb=args.ckandb, + datastoredb=args.datastoredb, + ckanuser=args.ckanuser, + writeuser=args.writeuser, + readonlyuser=args.readonlyuser + ) diff --git a/ckanext/datastore/bin/set_permissions.sql b/ckanext/datastore/bin/set_permissions.sql index d6db43b5dd8..08206449a3e 100644 --- a/ckanext/datastore/bin/set_permissions.sql +++ b/ckanext/datastore/bin/set_permissions.sql @@ -1,56 +1,54 @@ /* -This script creates a new datastore database and +This script sets-up the permissions for the the datastore. + +creates a new datastore database and a new read-only user for ckan who will only be able to select from the datastore database but has no create/write/edit permission or any permissions on other databases. -Please set the variables to you current setup. For testing purposes it +Please set the variables to you current set-up. For testing purposes it is possible to set maindb = datastoredb. To run the script, execute: - sudo -u postgres psql postgres -f create_read_only_user.sql + sudo -u postgres psql postgres -f set_permissions.sql */ -\set maindb "ckan" --- don't quote the datastoredb variable or create the database separately -\set datastoredb datastore -\set ckanuser ckanuser -\set rouser readonlyuser -\set ropwd 'pass' - --- create the datastore database -create database :datastoredb; - --- switch to the new database -\c :datastoredb; - -/* --- delete the previous users -REVOKE CONNECT ON DATABASE :datastoredb FROM :rouser; -DROP OWNED BY :rouser; -DROP USER :rouser; ---*/ - --- revoke permissions for the new user +-- name of the main CKAN database +\set maindb "{ckandb}" +-- the name of the datastore database +\set datastoredb '{datastoredb}' +-- username of the ckan postgres user +\set ckanuser '{ckanuser}' +-- username of the datastore user that can write +\set wuser '{writeuser}' +-- username of the datastore user who has only read permissions +\set rouser '{readonlyuser}' + +-- revoke permissions for the read-only user +---- this step can be ommitted if the datastore not +---- on the same server as the CKAN database REVOKE CREATE ON SCHEMA public FROM PUBLIC; REVOKE USAGE ON SCHEMA public FROM PUBLIC; GRANT CREATE ON SCHEMA public TO :ckanuser; GRANT USAGE ON SCHEMA public TO :ckanuser; --- create new read only user -CREATE USER :rouser WITH PASSWORD :ropwd NOSUPERUSER NOCREATEDB NOCREATEROLE LOGIN; +GRANT CREATE ON SCHEMA public TO :ckanuser; +GRANT USAGE ON SCHEMA public TO :ckanuser; --- take connect permissions from main db +-- take connect permissions from main CKAN db +---- again, this can be ommited if the read-only user can never have +---- access to the main CKAN database REVOKE CONNECT ON DATABASE :maindb FROM :rouser; -- grant select permissions for read-only user GRANT CONNECT ON DATABASE :datastoredb TO :rouser; GRANT USAGE ON SCHEMA public TO :rouser; --- grant access to current tables and views +-- grant access to current tables and views to read-only user GRANT SELECT ON ALL TABLES IN SCHEMA public TO :rouser; -- grant access to new tables and views by default -ALTER DEFAULT PRIVILEGES FOR USER :ckanuser IN SCHEMA public +---- the permissions will be set when the write user creates a table +ALTER DEFAULT PRIVILEGES FOR USER :wuser IN SCHEMA public GRANT SELECT ON TABLES TO :rouser; diff --git a/ckanext/datastore/commands.py b/ckanext/datastore/commands.py index 1a8bcdd06d6..3cc4a402022 100644 --- a/ckanext/datastore/commands.py +++ b/ckanext/datastore/commands.py @@ -1,5 +1,4 @@ -import re -import sys +import bin.datastore_setup as setup import logging import ckan.lib.cli as cli @@ -7,48 +6,13 @@ log = logging.getLogger(__name__) -read_only_user_sql = ''' --- revoke permissions for the new user -REVOKE CREATE ON SCHEMA public FROM PUBLIC; -REVOKE USAGE ON SCHEMA public FROM PUBLIC; - -GRANT CREATE ON SCHEMA public TO "{ckanuser}"; -GRANT USAGE ON SCHEMA public TO "{ckanuser}"; - -GRANT CREATE ON SCHEMA public TO "{writeuser}"; -GRANT USAGE ON SCHEMA public TO "{writeuser}"; - --- create new read only user -CREATE USER "{readonlyuser}" {with_password} NOSUPERUSER NOCREATEDB NOCREATEROLE LOGIN; - --- take connect permissions from main db -REVOKE CONNECT ON DATABASE "{maindb}" FROM "{readonlyuser}"; - --- grant select permissions for read-only user -GRANT CONNECT ON DATABASE "{datastore}" TO "{readonlyuser}"; -GRANT USAGE ON SCHEMA public TO "{readonlyuser}"; - --- grant access to current tables and views -GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{readonlyuser}"; - --- grant access to new tables and views by default -ALTER DEFAULT PRIVILEGES FOR USER "{writeuser}" IN SCHEMA public - GRANT SELECT ON TABLES TO "{readonlyuser}"; -''' - - class SetupDatastoreCommand(cli.CkanCommand): '''Perform commands to set up the datastore. Make sure that the datastore urls are set properly before you run these commands. - `create-all` will run create-write-user, create-db and create-read-only-user. Usage:: - paster datastore create-write-user SQL_SUPER_USER - paster datastore create-db SQL_SUPER_USER - paster datastore create-read-only-user SQL_SUPER_USER - - paster datastore create-all SQL_SUPER_USER + paster datastore set-permissions SQL_SUPER_USER Where: SQL_SUPER_USER is the name of a postgres user with sufficient @@ -79,89 +43,26 @@ def command(self): self.db_read_url_parts = cli.parse_db_config('ckan.datastore.read_url') self.db_ckan_url_parts = cli.parse_db_config('sqlalchemy.url') - assert self.db_write_url_parts['db_name'] == self.db_read_url_parts['db_name'], "write and read db should be the same" + assert self.db_write_url_parts['db_name'] == self.db_read_url_parts['db_name'],\ + "write and read db have to be the same" if len(self.args) != 2: print self.usage return self.sql_superuser = self.args[1] - if cmd == 'create-db': - self.create_db() - if self.verbose: - print 'Creating DB: SUCCESS' - elif cmd == 'create-read-only-user': - self.create_read_only_user() + if cmd == 'set-permissions': + setup.set_permissions( + pguser=self.sql_superuser, + ckandb=self.db_ckan_url_parts['db_name'], + datastoredb=self.db_write_url_parts['db_name'], + ckanuser=self.db_ckan_url_parts['db_user'], + writeuser=self.db_write_url_parts['db_user'], + readonlyuser=self.db_read_url_parts['db_user'] + ) if self.verbose: - print 'Creating read-only user: SUCCESS' - elif cmd == 'create-write-user': - self.create_write_user() - if self.verbose: - print 'Creating write user: SUCCESS' - elif cmd == 'create-all': - self.create_db() - if self.db_ckan_url_parts['db_user'] != self.db_write_url_parts['db_user']: - self.create_write_user() - self.create_read_only_user() - if self.verbose: - print 'Creating db and users for datastore: SUCCESS' + print 'Set permissions for read-only user: SUCCESS' else: print self.usage log.error('Command "%s" not recognized' % (cmd,)) return - - def _run_cmd(self, command_line, inputstring=''): - if self.verbose: - print "Running:", command_line - import subprocess - if not self.simulate: - p = subprocess.Popen( - command_line, shell=True, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout_value, stderr_value = p.communicate(input=inputstring) - if stderr_value: - print '\nAn error occured: {0}'.format(stderr_value) - sys.exit(1) - - def _run_sql(self, sql, as_sql_user, database='postgres'): - if self.verbose: - print "Executing: \n#####\n", sql, "\n####\nOn database:", database - if not self.simulate: - self._run_cmd("sudo -u '{username}' psql --dbname='{database}' -W".format( - username=as_sql_user, - database=database - ), inputstring=sql) - - def create_db(self): - cmd = "sudo -u {pguser} createdb -O '{ckanuser}' '{db}'".format( - pguser=self.sql_superuser, - db=self.db_write_url_parts['db_name'], - ckanuser=self.db_ckan_url_parts['db_user']) - self._run_cmd(cmd) - - def create_write_user(self): - cmd = "sudo -u {pguser} createuser \ - --no-createdb --no-createrole --no-superuser '{writeuser}'".format( - pguser=self.sql_superuser, - writeuser=self.db_write_url_parts['db_user']) - self._run_cmd(cmd) - - def create_read_only_user(self): - password = self.db_read_url_parts['db_pass'] - self.validate_password(password) - sql = read_only_user_sql.format( - maindb=self.db_ckan_url_parts['db_name'], - datastore=self.db_write_url_parts['db_name'], - ckanuser=self.db_ckan_url_parts['db_user'], - readonlyuser=self.db_read_url_parts['db_user'], - with_password="WITH PASSWORD '{0}'".format(password) if password else "", - writeuser=self.db_write_url_parts['db_user']) - self._run_sql(sql, - as_sql_user=self.sql_superuser, - database=self.db_write_url_parts['db_name']) - - def validate_password(self, password): - if "'" in password: - raise ValueError("Passwords cannot contain '")