Menerapkan Model Akses Berbasis Peran Menggunakan Keamanan Tingkat Baris di PostgreSQL

Pengembangan topik Etude tentang penerapan Row Level Secutity di PostgreSQL dan untuk jawaban rinci atas komentar tersebut.



Strategi yang digunakan mengasumsikan penggunaan konsep "Logika bisnis dalam database", yang dijelaskan lebih detail di sini - Esai tentang implementasi logika bisnis pada level fungsi tersimpan PostgreSQL. Bagian



teoretis dijelaskan dengan sempurna dalam dokumentasi Postgres Pro - Kebijakan keamanan baris . Di bawah ini kami mempertimbangkan implementasi praktis dari tugas bisnis tertentu - model akses data berbasis peran.





Artikel bukanlah hal baru, tidak ada makna tersembunyi dan pengetahuan rahasia. Sekadar sketsa tentang implementasi praktis dari ide teoritis. Jika ada yang tertarik, baca terus. Siapa yang tidak tertarik - jangan buang waktu Anda.


Rumusan masalah



Perlu membatasi akses untuk melihat / menyisipkan / memodifikasi / menghapus dokumen sesuai dengan peran pengguna aplikasi. Peran berarti entri dalam tabel peran yang terkait dalam hubungan banyak ke banyak dengan tabel pengguna . Rincian implementasi tabel dihilangkan karena hal sepele. Selain itu, detail implementasi khusus yang terkait dengan domain dihilangkan.



Penerapan



Kami membuat peran, skema, tabel



Membuat objek database
CREATE ROLE store;
CREATE SCHEMA store AUTHORIZATION store;
CREATE TABLE store.docs
(
  id integer ,         --id 
  man_id integer , --id  
  stat_id integer ,  --id  
  ...
  is_del BOOLEAN DEFAULT FALSE 
);
ALTER TABLE store.docs ADD CONSTRAINT doc_pk PRIMARY KEY (id);
ALTER TABLE store.docs OWNER TO store ;


Membuat fungsi untuk mengimplementasikan RLS



Memeriksa apakah suatu baris adalah SELECT



check_select
CREATE OR REPLACE FUNCTION store.check_select ( current_id store.docs.id%TYPE ) RETURNS boolean AS $$
DECLARE
  result boolean ;
  curr_pid integer ;
  curr_stat_id integer ;
  doc_man_id integer ;
BEGIN 
  -- DBA     
  IF SESSION_USER = 'curr_dba'
  THEN
    RETURN TRUE ;
  END IF ;
  --------------------------------

  --    '' -    
  SELECT
    is_del
  INTO
    result
  FROM
    store.docs
  WHERE
    id = current_id ;
 IF result = TRUE
 THEN
   RETURN FALSE ;
 END IF ;
 --------------------------------

 -- id  
 SELECT
   service_function.get_curr_pid ()
 INTO
   curr_pid ;
 --------------------------------

 -- id  
 SELECT
   man_id
 INTO
   doc_man_id
 FROM
   store.docs
 WHERE
   id = current_id ;
 --------------------------------

 --         
 --   
 IF doc_man_id != curr_pid OR doc_man_id IS NULL
 THEN
   RETURN TRUE  ;
 ELSE
   --   
   SELECT
     stat_id                                         
   INTO
     curr_statid
   FROM
     store.docs
   WHERE
     id = current_id ;
    
   --     -                         
   IF curr_statid = 4 OR curr_statid = 9
   THEN
     RETURN TRUE ;
   ELSE
   -- -    
     RETURN FALSE ;
    END IF ;
  END IF ;
  --------------------------------

 RETURN FALSE ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.check_select( store.docs.id%TYPE  ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.check_select( store.docs.id%TYPE  ) FROM public; 
GRANT EXECUTE ON FUNCTION store.check_select( store.docs.id%TYPE  ) TO service_functions; 


Memeriksa INSERT Rows



check_insert
CREATE OR REPLACE FUNCTION store.check_insert ( current_id store.docs.id%TYPE ) RETURNS boolean AS $$
DECLARE
  curr_role_id integer ;
BEGIN
  --DBA      
  IF SESSION_USER = 'curr_dba'
  THEN
    RETURN TRUE ;
  END IF ;
  --------------------------------

 -- id    
 SELECT
   service_functions.current_rid()
  INTO
    curr_role_id ;
 --------------------------------

--      
--
IF curr_role_id = 3 OR curr_role_id = 5     
THEN
  RETURN TRUE ;
END IF ;
--------------------------------
RETURN FALSE  ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.check_insert( store.docs.id%TYPE  ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.check_insert( store.docs.id%TYPE  ) FROM public;
GRANT EXECUTE ON FUNCTION store.check_insert( store.docs.id%TYPE  ) TO service_functions; 


Memeriksa apakah baris bisa DIHAPUS



check_delete
CREATE OR REPLACE FUNCTION store.check_delete ( current_id store.docs.id%TYPE )
RETURNS boolean AS $$
BEGIN  
  -- DBA    
  IF SESSION_USER = 'curr_dba'
  THEN
    RETURN TRUE ;
  END IF ;
  --------------------------------

  RETURN FALSE ;
END
$$ LANGUAGE plpgsql
SECURITY DEFINER;
ALTER FUNCTION store.check_delete( store.docs.id%TYPE  ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.check_delete( store.docs.id%TYPE  ) FROM public;


Memeriksa apakah baris tersebut dapat DIPERBARUI.



update_using
CREATE OR REPLACE FUNCTION store.update_using ( current_id store.docs.id%TYPE , is_del boolean  )
RETURNS boolean AS $$
BEGIN  
   --   '' -  
   IF is_del 
   THEN
     RETURN FALSE ;
 ELSE
    RETURN TRUE ;
  END IF ;

END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.update_using(  store.docs.id%TYPE ,  boolean  ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.update_using(  store.docs.id%TYPE ,  boolean  ) FROM public;
GRANT EXECUTE ON FUNCTION store.update_using( store.docs.id%TYPE  ) TO service_functions;


update_check
CREATE OR REPLACE FUNCTION store.update_with_check ( current_id store.docs.id%TYPE , is_del boolean )
RETURNS boolean AS $$
DECLARE
  current_rid integer ;
  current_statid integer ;
BEGIN                

  --DBA    
  IF SESSION_USER = 'curr_dba'
  THEN
    RETURN TRUE ;
  END IF ;
  --------------------------------

 -- id    
 SELECT
   service_functions.current_rid()
  INTO
    curr_role_id ;
 --------------------------------                            

 --  -   
 IF is_deleted
 THEN
   --   ***
   IF current_role_id = 3        
   THEN
      SELECT
        stat_id                                          
      INTO
        curr_statid
      FROM
        store.docs
      WHERE
        id = current_id ;

      --   ***   
      IF current_status_id = 11
      THEN
         RETURN FALSE ;
      ELSE
      --     
        RETURN TRUE ;
      END IF ;

    -- ,    ***
    ELSIF current_role_id = 5            
    THEN
      --   
      RETURN TRUE ;
    ELSE
      --     
      RETURN FALSE ;
    END IF ;
 ELSE      
   --  
    RETURN TRUE ;
END IF ;

RETURN FALSE ;
END
$$ LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION store.update_with_check( storg.docs.id%TYPE ,  boolean   ) OWNER TO store ;
REVOKE EXECUTE ON FUNCTION store.update_with_check( storg.docs.id%TYPE ,  boolean   )  FROM public;
GRANT EXECUTE ON FUNCTION store.update_with_check( store.docs.id%TYPE  ) TO service_functions;


Mengaktifkan kebijakan Keamanan Tingkat Baris untuk tabel.



AKTIFKAN KEAMANAN ROW LEVEL
ALTER TABLE store.docs ENABLE ROW LEVEL SECURITY ;

CREATE POLICY doc_select ON store.docs FOR SELECT TO service_functions USING ( (SELECT store.check_select(id)) );
CREATE POLICY doc_insert ON store.docs FOR INSERT TO service_functions WITH CHECK ( (SELECT store.check_insert(id)) );
CREATE POLICY docs_delete ON store.docs FOR DELETE TO service_functions USING ( (SELECT store.check_delete(id)) );

CREATE POLICY doc_update_using ON store.docs FOR UPDATE TO service_functions USING ( (SELECT store.update_using(id , is_del )) );
CREATE POLICY doc_update_check ON store.docs FOR UPDATE TO service_functions  WITH CHECK ( (SELECT store.update_with_check(id , is_del )) );


Hasil



Berhasil.



Strategi yang diusulkan memungkinkan kami untuk memindahkan implementasi model peran dari tingkat fungsi bisnis ke tingkat penyimpanan data.



Fungsi dapat digunakan sebagai template untuk mengimplementasikan model penyembunyian data yang lebih canggih jika persyaratan bisnis memerlukannya.



All Articles