Commit 86b72fd7 authored by Marcin's avatar Marcin
Browse files

introducing authorization

Two roles (groups ) are introduced: hived_group, hive_applications_group
roles which inherit from hived_group must be used by hived process
roles which inherit from hive_applications_group must be used by
application
parent 2a3de1cc
......@@ -17,4 +17,5 @@ ADD_PSQL_EXTENSION(
app_api_impl.sql
hived_api.sql
app_api.sql
authorization.sql
)
\ No newline at end of file
......@@ -17,7 +17,22 @@ To start using the extension in a database, execute psql command: `CREATE EXTENS
### Alternatively, you can manually execute the SQL scripts to directly install the fork manager
The required ordering of the sql scripts is included in the cmake file [src/hive_fork/CMakeLists.txt](./CMakeLists.txt).
Execute each script one-by-one with `psql` as in this example: `psql -d my_db_name -a -f context_rewind/data_schema.sql`
### Authorization
During its creation the extension introduces two new roles (groups): `hived_group` and `hive_application_group`. The maintainer of
the PostgreSQL cluster server needs to create roles ( users ) which inherits from one of these groups.
```
CREATE ROLE hived LOGIN PASSWORD 'hivedpass' INHERIT IN ROLE hived_group;
CREATE ROLE application LOGIN PASSWORD 'applicationpass' INHERIT IN ROLE hive_applications_group;
```
The roles which inherits
from `hived_groups` must be used by `hived` process to login into the database, roles which inherit from `hive_application_group` shall
be used by the applications. Each application role does not have access to internal data created by other application roles and cannot
modify data modified by the 'hived'. 'Hived' roles cannot modify the applications data.
More about roles in PostgreSQL documentaion: [CREATE ROLE](https://www.postgresql.org/docs/10/sql-createrole.html)
## Architecture
All elements of the fork manager are placed in a schema called 'hive'.
......@@ -42,7 +57,11 @@ are enough to automatically create views which combine irreversible and reversib
### Requirements for an application algorithm using the fork manager API
![alt text](./doc/evq_app_process_block.png)
Any application must first create a context, then create its tables which inherit from `hive.base`.
Only roles ( users ) which inherits from 'hive_applications_group' have access to 'The APP API', and only these roles allow
applications to work with 'hive_fork_menager'
Any application must first create a context, then create its tables which inherit from `hive.base`. The context is owned
and can be accessed only by the role which created it.
An application calls `hive.app_next_block` to get the next block number to process. If NULL was returned, an application must immediatly call `hive.app_next_block` again. Note: the application will automatically be blocked when it calls `hive.app_next_block` if there are no blocks to process.
......
......@@ -13,8 +13,9 @@ BEGIN
, irreversible_block
, is_attached
, events_id
, fork_id)
SELECT cdata.name, cdata.block_num, COALESCE( hb.num, 0 ), cdata.is_attached, cdata.events_id, hf.id
, fork_id
, owner)
SELECT cdata.name, cdata.block_num, COALESCE( hb.num, 0 ), cdata.is_attached, cdata.events_id, hf.id, current_user
FROM
( VALUES ( _name, 0, TRUE, NULL::BIGINT ) ) as cdata( name, block_num, is_attached, events_id )
JOIN ( SELECT hf.id FROM hive.fork hf ORDER BY id DESC LIMIT 1 ) as hf ON TRUE
......
DO $$
BEGIN
CREATE ROLE hived_group WITH NOLOGIN;
EXCEPTION WHEN DUPLICATE_OBJECT THEN
RAISE NOTICE 'hived_group role already exists';
END
$$;
DO $$
BEGIN
CREATE ROLE hive_applications_group WITH NOLOGIN;
EXCEPTION WHEN DUPLICATE_OBJECT THEN
RAISE NOTICE 'hive_applications_group role already exists';
END
$$;
-- generic protection for tables in hive schema
-- 1. hived_group allow to edit every table in hive schema
-- 2. hive_applications_group can ready every table in hive schema
-- 3. hive_applications_group can modify hive.contexts, hive.registered_tables, hive.triggers
GRANT ALL ON SCHEMA hive to hived_group, hive_applications_group;
GRANT ALL ON ALL SEQUENCES IN SCHEMA hive TO hived_group, hive_applications_group;
GRANT ALL ON ALL TABLES IN SCHEMA hive TO hived_group;
GRANT SELECT ON ALL TABLES IN SCHEMA hive TO hive_applications_group;
GRANT ALL ON hive.contexts TO hive_applications_group;
GRANT ALL ON hive.registered_tables TO hive_applications_group;
GRANT ALL ON hive.triggers TO hive_applications_group;
-- protect an application rows aginst other applications
ALTER TABLE hive.contexts ENABLE ROW LEVEL SECURITY;
CREATE POLICY dp_hive_context ON hive.contexts FOR ALL USING ( owner = current_user );
CREATE POLICY sp_hived_hive_context ON hive.contexts FOR SELECT TO hived_group USING( TRUE );
CREATE POLICY sp_applications_hive_context ON hive.contexts FOR SELECT TO hive_applications_group USING( owner = current_user );
ALTER TABLE hive.registered_tables ENABLE ROW LEVEL SECURITY;
CREATE POLICY policy_hive_registered_tables ON hive.registered_tables FOR ALL USING ( owner = current_user );
ALTER TABLE hive.triggers ENABLE ROW LEVEL SECURITY;
CREATE POLICY policy_hive_triggers ON hive.triggers FOR ALL USING ( owner = current_user );
-- protect api
-- 1. only hived_group and hive_applications_group can invoke functions from hive schema
-- 2. hived_group can use only hived_api
-- 3. hive_applications_group can use every functions from hive schema except hived_api
REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA hive FROM PUBLIC;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA hive TO hive_applications_group;
GRANT EXECUTE ON FUNCTION
hive.back_from_fork( INT )
, hive.push_block( hive.blocks, hive.transactions[], hive.transactions_multisig[], hive.operations[] )
, hive.set_irreversible( INT )
, hive.end_massive_sync()
, hive.copy_blocks_to_irreversible( _head_block_of_irreversible_blocks INT, _new_irreversible_block INT )
, hive.copy_transactions_to_irreversible( _head_block_of_irreversible_blocks INT, _new_irreversible_block INT )
, hive.copy_operations_to_irreversible( _head_block_of_irreversible_blocks INT, _new_irreversible_block INT )
, hive.copy_signatures_to_irreversible( _head_block_of_irreversible_blocks INT, _new_irreversible_block INT )
, hive.remove_obsolete_reversible_data( _new_irreversible_block INT )
, hive.remove_unecessary_events( _new_irreversible_block INT )
TO hived_group;
REVOKE EXECUTE ON FUNCTION
hive.back_from_fork( INT )
, hive.push_block( hive.blocks, hive.transactions[], hive.transactions_multisig[], hive.operations[] )
, hive.set_irreversible( INT )
, hive.end_massive_sync()
, hive.copy_blocks_to_irreversible( _head_block_of_irreversible_blocks INT, _new_irreversible_block INT )
, hive.copy_transactions_to_irreversible( _head_block_of_irreversible_blocks INT, _new_irreversible_block INT )
, hive.copy_operations_to_irreversible( _head_block_of_irreversible_blocks INT, _new_irreversible_block INT )
, hive.copy_signatures_to_irreversible( _head_block_of_irreversible_blocks INT, _new_irreversible_block INT )
, hive.remove_obsolete_reversible_data( _new_irreversible_block INT )
, hive.remove_unecessary_events( _new_irreversible_block INT )
FROM hive_applications_group;
......@@ -12,10 +12,13 @@ CREATE TABLE IF NOT EXISTS hive.contexts(
back_from_fork BOOL NOT NULL DEFAULT FALSE,
events_id BIGINT, -- no event is processed
fork_id BIGINT NOT NULL DEFAULT 1,
owner NAME NOT NULL,
CONSTRAINT pk_hive_contexts PRIMARY KEY( id ),
CONSTRAINT uq_hive_context_name UNIQUE ( name )
);
CREATE INDEX IF NOT EXISTS hive_contexts_owner_idx ON hive.contexts( owner );
CREATE TABLE IF NOT EXISTS hive.registered_tables(
id SERIAL NOT NULL,
context_id INTEGER NOT NULL,
......@@ -23,22 +26,31 @@ CREATE TABLE IF NOT EXISTS hive.registered_tables(
origin_table_name TEXT NOT NULL,
shadow_table_name TEXT NOT NULL,
origin_table_columns TEXT[] NOT NULL,
owner NAME NOT NULL,
CONSTRAINT pk_hive_registered_tables PRIMARY KEY( id ),
CONSTRAINT fk_hive_registered_tables_context FOREIGN KEY(context_id) REFERENCES hive.contexts( id ),
CONSTRAINT uq_hive_registered_tables_register_table UNIQUE( origin_table_schema, origin_table_name )
);
CREATE INDEX IF NOT EXISTS hive_registered_tables_context_id ON hive.registered_tables( context_id );
CREATE INDEX IF NOT EXISTS hive_registered_tables_context_idx ON hive.registered_tables( context_id );
CREATE INDEX IF NOT EXISTS hive_registered_tables_owder_idx ON hive.registered_tables( owner );
CREATE TABLE IF NOT EXISTS hive.triggers(
id SERIAL PRIMARY KEY,
registered_table_id INTEGER NOT NULL,
trigger_name TEXT NOT NULL,
function_name TEXT NOT NULL,
owner NAME NOT NULL,
CONSTRAINT fk_hive_triggers_registered_table FOREIGN KEY( registered_table_id ) REFERENCES hive.registered_tables( id ),
CONSTRAINT uq_hive_triggers_registered_table UNIQUE( trigger_name )
);
CREATE INDEX IF NOT EXISTS hive_registered_triggers_table_id ON hive.triggers( registered_table_id );
CREATE INDEX IF NOT EXISTS hive_triggers_owner_idx ON hive.triggers( owner );
......@@ -128,8 +128,8 @@ BEGIN
SELECT hive.create_shadow_table( _table_schema, _table_name ) INTO __shadow_table_name;
-- insert information about new registered table
INSERT INTO hive.registered_tables( context_id, origin_table_schema, origin_table_name, shadow_table_name, origin_table_columns )
SELECT hc.id, tables.table_schema, tables.origin, tables.shadow, columns
INSERT INTO hive.registered_tables( context_id, origin_table_schema, origin_table_name, shadow_table_name, origin_table_columns, owner )
SELECT hc.id, tables.table_schema, tables.origin, tables.shadow, columns, current_user
FROM ( SELECT hc.id FROM hive.contexts hc WHERE hc.name = _context_name ) as hc
JOIN ( VALUES( lower(_table_schema), lower(_table_name), __shadow_table_name, __columns_names ) ) as tables( table_schema, origin, shadow, columns ) ON TRUE
RETURNING context_id, id INTO __context_id, __registered_table_id
......@@ -331,12 +331,12 @@ BEGIN
PERFORM hive.create_revert_functions( _table_schema, _table_name, __shadow_table_name, __columns_names );
-- save information about the triggers
INSERT INTO hive.triggers( registered_table_id, trigger_name, function_name )
INSERT INTO hive.triggers( registered_table_id, trigger_name, function_name, owner )
VALUES
( __registered_table_id, __hive_insert_trigger_name, __hive_triggerfunction_name_insert )
, ( __registered_table_id, __hive_delete_trigger_name, __hive_triggerfunction_name_delete )
, ( __registered_table_id, __hive_update_trigger_name, __hive_triggerfunction_name_update )
, ( __registered_table_id, __hive_truncate_trigger_name, __hive_triggerfunction_name_truncate )
( __registered_table_id, __hive_insert_trigger_name, __hive_triggerfunction_name_insert, current_user )
, ( __registered_table_id, __hive_delete_trigger_name, __hive_triggerfunction_name_delete, current_user )
, ( __registered_table_id, __hive_update_trigger_name, __hive_triggerfunction_name_update, current_user )
, ( __registered_table_id, __hive_truncate_trigger_name, __hive_triggerfunction_name_truncate, current_user )
;
END;
$BODY$
......
......@@ -11,7 +11,7 @@ BEGIN
END IF;
EXECUTE format( 'CREATE TABLE hive.%I( hive_rowid BIGSERIAL )', _name );
INSERT INTO hive.contexts( name, current_block_num, irreversible_block, is_attached ) VALUES( _name, 0, 0, TRUE );
INSERT INTO hive.contexts( name, current_block_num, irreversible_block, is_attached, owner ) VALUES( _name, 0, 0, TRUE, current_user );
END;
$BODY$
;
......
......@@ -13,6 +13,21 @@ MACRO( ADD_SQL_FUNCTIONAL_TESTS sql_test_path)
MESSAGE( STATUS "Added functional tests '${test_target}'" )
ENDMACRO()
MACRO( ADD_AUTHORIZATION_FUNCTIONAL_TESTS sql_test_path)
STRING( REGEX MATCH "^(([^\\/]*)\\/)?(.*)\\.[^.]*$" dummy ${sql_test_path} ) # remove sql extension
SET( test_name ${CMAKE_MATCH_3})
IF( CMAKE_MATCH_2 )
SET( test_name ${CMAKE_MATCH_2}.${test_name} )
ENDIF()
SET( test_target test.functional.hive_fork.${test_name} )
SET( sources_under_tests_path ${CMAKE_SOURCE_DIR}/src/hive_fork )
ADD_TEST( NAME ${test_target}
COMMAND test_authorization.sh ${sources_under_tests_path} ${sql_test_path}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
MESSAGE( STATUS "Added functional authorization tests '${test_target}'" )
ENDMACRO()
ADD_SQL_FUNCTIONAL_TESTS( context_rewind/schema_test.sql )
ADD_SQL_FUNCTIONAL_TESTS( context_rewind/register_table_test.sql )
......@@ -136,4 +151,11 @@ ADD_SQL_FUNCTIONAL_TESTS( app_api/register_already_existed_table.sql )
ADD_SQL_FUNCTIONAL_TESTS( app_api/register_already_registered_table_negative.sql )
ADD_SQL_FUNCTIONAL_TESTS( scenarios/massive_sync_live_blocks_and_fork.sql )
ADD_SQL_FUNCTIONAL_TESTS( scenarios/massive_sync_live_blocks_and_fork_non_forking_app.sql )
\ No newline at end of file
ADD_SQL_FUNCTIONAL_TESTS( scenarios/massive_sync_live_blocks_and_fork_non_forking_app.sql )
ADD_AUTHORIZATION_FUNCTIONAL_TESTS( authorization/alice_access_to_bob_negative.sql )
ADD_AUTHORIZATION_FUNCTIONAL_TESTS( authorization/alice_access_events_infrustructure.sql )
ADD_AUTHORIZATION_FUNCTIONAL_TESTS( authorization/hived_access_alice_context_data.sql )
ADD_AUTHORIZATION_FUNCTIONAL_TESTS( authorization/api_protection.sql )
ADD_AUTHORIZATION_FUNCTIONAL_TESTS( authorization/massive_sync_live_blocks_and_fork.sql )
\ No newline at end of file
DROP FUNCTION IF EXISTS hived_test_given;
CREATE FUNCTION hived_test_given()
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
AS
$BODY$
BEGIN
INSERT INTO hive.blocks
VALUES
( 1, '\xBADD10', '\xCAFE10', '2016-06-22 19:10:21-07'::timestamp )
, ( 2, '\xBADD20', '\xCAFE20', '2016-06-22 19:10:22-07'::timestamp )
, ( 3, '\xBADD30', '\xCAFE30', '2016-06-22 19:10:23-07'::timestamp )
, ( 4, '\xBADD40', '\xCAFE40', '2016-06-22 19:10:24-07'::timestamp )
, ( 5, '\xBADD50', '\xCAFE50', '2016-06-22 19:10:25-07'::timestamp )
;
PERFORM hive.end_massive_sync();
END;
$BODY$
;
DROP FUNCTION IF EXISTS hived_test_when;
CREATE FUNCTION hived_test_when()
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
AS
$BODY$
BEGIN
-- EXECUTE ACTION UDER TEST AS HIVED
END;
$BODY$
;
DROP FUNCTION IF EXISTS hived_test_then;
CREATE FUNCTION hived_test_then()
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
AS
$BODY$
BEGIN
-- CHECK EXPECTED STATE AS HIVED
END;
$BODY$
;
DROP FUNCTION IF EXISTS alice_test_given;
CREATE FUNCTION alice_test_given()
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
AS
$BODY$
BEGIN
-- PREPARE STATE AS ALICE
END;
$BODY$
;
DROP FUNCTION IF EXISTS alice_test_when;
CREATE FUNCTION alice_test_when()
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
AS
$BODY$
BEGIN
-- EXECUTE ACTION UDER TEST AS ALICE
END;
$BODY$
;
DROP FUNCTION IF EXISTS alice_test_then;
CREATE FUNCTION alice_test_then()
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
AS
$BODY$
BEGIN
BEGIN
DELETE FROM hive.blocks;
ASSERT FALSE, 'Alice can delete irreversible blocks';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
DELETE FROM hive.transactions_multisig;
ASSERT FALSE, 'Alice can delete irreversible transactions_multisig';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
DELETE FROM hive.transactions;
ASSERT FALSE, 'Alice can delete irreversible transactions';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
DELETE FROM hive.operation_types;
ASSERT FALSE, 'Alice can delete irreversible operation_types';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
DELETE FROM hive.operations;
ASSERT FALSE, 'Alice can delete irreversible operations';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
DELETE FROM hive.fork;
ASSERT FALSE, 'Alice can delete hive.fork';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
INSERT INTO hive.fork VALUES( 1, 15, now() );
ASSERT FALSE, 'Alice can insert to hive.fork';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
UPDATE hive.fork SET num = 10;
ASSERT FALSE, 'Alice can update to hive.fork';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
DROP TABLE hive.fork;
ASSERT FALSE, 'Alice can drop hive.fork';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
DELETE FROM hive.events_queue;
ASSERT FALSE, 'Alice can delete hive.events_queue';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
INSERT INTO hive.events_queue VALUES( 1, 'MASSIVE_SYNC', 10 );
ASSERT FALSE, 'Alice can insert to hive.events_queue';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
UPDATE hive.events_queue SET event = 'MASSIVE_SYNC';
ASSERT FALSE, 'Alice can update to hive.events_queue';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
DROP TABLE hive.events_queue;
ASSERT FALSE, 'Alice can drop hive.events_queue';
EXCEPTION WHEN OTHERS THEN
END;
END;
$BODY$
;
DROP FUNCTION IF EXISTS bob_test_given;
CREATE FUNCTION bob_test_given()
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
AS
$BODY$
BEGIN
-- PREPARE STATE AS BOB
END;
$BODY$
;
DROP FUNCTION IF EXISTS bob_test_when;
CREATE FUNCTION bob_test_when()
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
AS
$BODY$
BEGIN
-- EXECUTE ACTION UDER TEST AS BOB
END;
$BODY$
;
DROP FUNCTION IF EXISTS bob_test_then;
CREATE FUNCTION bob_test_then()
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
AS
$BODY$
BEGIN
-- CHECK EXPECTED STATE AS BOB
END;
$BODY$
;
DROP FUNCTION IF EXISTS hived_test_given;
CREATE FUNCTION hived_test_given()
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
AS
$BODY$
BEGIN
INSERT INTO hive.blocks
VALUES
( 1, '\xBADD10', '\xCAFE10', '2016-06-22 19:10:21-07'::timestamp )
, ( 2, '\xBADD20', '\xCAFE20', '2016-06-22 19:10:22-07'::timestamp )
, ( 3, '\xBADD30', '\xCAFE30', '2016-06-22 19:10:23-07'::timestamp )
, ( 4, '\xBADD40', '\xCAFE40', '2016-06-22 19:10:24-07'::timestamp )
, ( 5, '\xBADD50', '\xCAFE50', '2016-06-22 19:10:25-07'::timestamp )
;
PERFORM hive.end_massive_sync();
END;
$BODY$
;
DROP FUNCTION IF EXISTS hived_test_when;
CREATE FUNCTION hived_test_when()
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
AS
$BODY$
BEGIN
-- EXECUTE ACTION UDER TEST AS HIVED
END;
$BODY$
;
DROP FUNCTION IF EXISTS hived_test_then;
CREATE FUNCTION hived_test_then()
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
AS
$BODY$
BEGIN
-- CHECK EXPECTED STATE AS HIVED
END;
$BODY$
;
DROP FUNCTION IF EXISTS alice_test_given;
CREATE FUNCTION alice_test_given()
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
AS
$BODY$
BEGIN
PERFORM hive.app_create_context( 'alice_context' );
PERFORM hive.app_create_context( 'alice_context_detached' );
PERFORM hive.app_context_detach( 'alice_context_detached' );
CREATE TABLE alice_table( id INT ) INHERITS( hive.alice_context );
PERFORM hive.app_next_block( 'alice_context' );
INSERT INTO alice_table VALUES( 10 );
END;
$BODY$
;
DROP FUNCTION IF EXISTS alice_test_when;
CREATE FUNCTION alice_test_when()
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
AS
$BODY$
BEGIN
-- EXECUTE ACTION UDER TEST AS ALICE
END;
$BODY$
;
DROP FUNCTION IF EXISTS alice_test_then;
CREATE FUNCTION alice_test_then()
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
AS
$BODY$
BEGIN
BEGIN
CREATE TABLE bob_in_bob_context(id INT ) INHERITS( hive.bob_context );
ASSERT FALSE, 'Alice can create table in Bob''s context';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
PERFORM hive.app_next_block( 'bob_context' );
ASSERT FALSE, 'Alice can move forward Bob'' context';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
PERFORM hive.app_context_detach( 'bob_context' );
ASSERT FALSE, 'Alice can detach Bob''s context';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
PERFORM hive.app_context_detach( 'bob_context' );
ASSERT FALSE, 'Alice can detach Bob''s context';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
PERFORM hive.app_context_attach( 'bob_context_detached', 1 );
ASSERT FALSE, 'Alice can attach Bob''s context';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
PERFORM hive.app_create_context( 'bob_context' );
ASSERT FALSE, 'Alice can override Bob''s context';
EXCEPTION WHEN OTHERS THEN
END;
BEGIN
PERFORM * FROM alice_table;
EXCEPTION WHEN OTHERS THEN
ASSERT FALSE, 'Alice cannot read her own table';