Skip to content

Commit

Permalink
Change the set-up process as recommended by @kindly
Browse files Browse the repository at this point in the history
  • Loading branch information
domoritz authored and amercader committed Oct 12, 2012
1 parent 0632eaa commit bf489d0
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 142 deletions.
Empty file.
79 changes: 79 additions & 0 deletions 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
)
56 changes: 27 additions & 29 deletions 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;
127 changes: 14 additions & 113 deletions ckanext/datastore/commands.py
@@ -1,54 +1,18 @@
import re
import sys
import bin.datastore_setup as setup
import logging

import ckan.lib.cli as cli

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
Expand Down Expand Up @@ -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 '")

0 comments on commit bf489d0

Please sign in to comment.