PostgreSQL'de mevcut kullanıcıyı görünümler ve tetikleyiciler aracılığıyla izleme


11

Geçerli kullanıcıya bağlı olarak kayıtlara erişimi sınırlayan ve kullanıcı tarafından yapılan değişiklikleri izleyen bir PostgreSQL (9.4) veritabanı var. Bu, görünümler ve tetikleyiciler aracılığıyla elde edilir ve çoğunlukla bu iyi çalışır, ancak INSTEAD OFtetikleyici gerektiren görünümlerde sorun yaşıyorum . Sorunu azaltmaya çalıştım, ama bunun hala uzun olduğu için özür dilerim.

Durum

Veritabanına tüm bağlantılar, tek bir hesap aracılığıyla web ön ucundan yapılır dbweb. Bağlantı kurulduktan sonra rol, SET ROLEweb arayüzünü kullanan kişiye karşılık gelecek şekilde değiştirilir ve bu tür tüm roller grup rolüne aittir dbuser. (Ayrıntılar için bu cevaba bakınız). Kullanıcının olduğunu varsayalım alice.

Tablolarımın çoğu burada arayacağım privateve ait olacağım bir şemaya yerleştirildi dbowner. Bu tablolara doğrudan erişilemez dbuser, ancak başka bir rol için erişilebilir dbview. Örneğin:

SET SESSION AUTHORIZATION dbowner;
CREATE TABLE private.incident
(
  incident_id serial PRIMARY KEY,
  incident_name character varying NOT NULL,
  incident_owner character varying NOT NULL
);
GRANT ALL ON TABLE private.incident TO dbview;

Geçerli satırda belirli satırların kullanılabilirliği alicediğer görünümler tarafından belirlenir. Basitleştirilmiş bir örnek (azaltılabilir, ancak daha genel vakaları desteklemek için bu şekilde yapılması gerekir):

-- Simplified case, but in principle could join multiple tables to determine allowed ids
CREATE OR REPLACE VIEW usr_incident AS 
 SELECT incident_id
   FROM private.incident
  WHERE incident_owner  = current_user;
ALTER TABLE usr_incident
  OWNER TO dbview;

Daha sonra satırlara erişim, aşağıdaki dbuserrollere erişilebilen bir görünümle sağlanır alice:

CREATE OR REPLACE VIEW public.incident AS 
 SELECT incident.*
   FROM private.incident
  WHERE (incident_id IN ( SELECT incident_id
           FROM usr_incident));
ALTER TABLE public.incident
  OWNER TO dbview;
GRANT ALL ON TABLE public.incident TO dbuser;

FROMBu maddede yalnızca bir ilişki göründüğünden , bu tür bir görünümün ek tetikleyiciler olmadan güncellenebilir olduğunu unutmayın.

Günlüğe kaydetme için, hangi tablonun değiştirildiğini ve kimin değiştirildiğini kaydetmek için başka bir tablo vardır. İndirgenmiş sürüm:

CREATE TABLE private.audit
(
  audit_id serial PRIMATE KEY,
  table_name text NOT NULL,
  user_name text NOT NULL
);
GRANT INSERT ON TABLE private.audit TO dbuser;

Bu, izlemek istediğim ilişkilerin her birine yerleştirilen tetikleyicilerle doldurulur. Örneğin, private.incidentyalnızca kesici uçlarla sınırlı bir örnek :

CREATE OR REPLACE FUNCTION private.if_modified_func()
  RETURNS trigger AS
$BODY$
BEGIN
    IF TG_OP = 'INSERT' THEN
        INSERT INTO private.audit (table_name, user_name)
        VALUES (tg_table_name::text, current_user::text);
        RETURN NEW;
    END IF;
END;
$BODY$
  LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION private.if_modified_func() TO dbuser;

CREATE TRIGGER log_incident
AFTER INSERT ON private.incident
FOR EACH ROW
EXECUTE PROCEDURE private.if_modified_func();

Yani şimdi eğer aliceiçine ekler public.incident, rekor ('incident','alice')denetim görünür.

Sorun

Bu yaklaşım, görünümler daha karmaşık hale geldiğinde ve INSTEAD OFekleri desteklemek için tetikleyicilere ihtiyaç duyulduğunda sorunlara neden olur .

Diyelim ki iki ilişkim var, örneğin bire bir ilişkide yer alan varlıkları temsil eden:

CREATE TABLE private.driver
(
  driver_id serial PRIMARY KEY,
  driver_name text NOT NULL
);
GRANT ALL ON TABLE private.driver TO dbview;

CREATE TABLE private.vehicle
(
  vehicle_id serial PRIMARY KEY,
  incident_id integer REFERENCES private.incident,
  make text NOT NULL,
  model text NOT NULL,
  driver_id integer NOT NULL REFERENCES private.driver
);
GRANT ALL ON TABLE private.vehicle TO dbview;

Adından başka ayrıntıları göstermek istemediğimizi varsayalım private.driverve bu yüzden tablolara katılan ve ortaya çıkarmak istediğim bitleri yansıtan bir görünüme sahibim:

CREATE OR REPLACE VIEW public.vehicle AS 
 SELECT vehicle_id, make, model, driver_name
   FROM private.driver
   JOIN private.vehicle USING (driver_id)
  WHERE (incident_id IN ( SELECT incident_id
               FROM usr_incident));
ALTER TABLE public.vehicle OWNER TO dbview;
GRANT ALL ON TABLE public.vehicle TO dbuser;

İçin için alicebir tetikleyici sağlanacak olan bu görüş, örneğin içine yerleştirmek mümkün:

CREATE OR REPLACE FUNCTION vehicle_vw_insert()
  RETURNS trigger AS
$BODY$
DECLARE did INTEGER;
   BEGIN
     INSERT INTO private.driver(driver_name) VALUES(NEW.driver_name) RETURNING driver_id INTO did;
     INSERT INTO private.vehicle(make, model, driver_id) VALUES(NEW.make_id,NEW.model, did) RETURNING vehicle_id INTO NEW.vehicle_id;
     RETURN NEW;
    END;
$BODY$
  LANGUAGE plpgsql SECURITY DEFINER;
ALTER FUNCTION vehicle_vw_insert()
  OWNER TO dbowner;
GRANT EXECUTE ON FUNCTION vehicle_vw_insert() TO dbuser;

CREATE TRIGGER vehicle_vw_insert_trig
INSTEAD OF INSERT ON public.vehicle
FOR EACH ROW
EXECUTE PROCEDURE vehicle_vw_insert();

Bununla ilgili sorun SECURITY DEFINER, tetikleyici işlevindeki seçeneğin current_userayarlanmış olarak çalıştırılmasına neden olmasıdır dbowner, bu nedenle alicegörünüme yeni bir kayıt private.auditeklerse, yazarın kayıtlarında karşılık gelen girdi olur dbowner.

Peki, gruba şemadaki ilişkilere doğrudan erişim current_uservermeden bunu korumanın bir yolu var mı? dbuserprivate

Kısmi Çözüm

Craig'in önerdiği gibi, tetikleyiciler yerine kurallar kullanmak current_user. Yukarıdaki örnek kullanılarak, güncelleme tetikleyicisi yerine aşağıdakiler kullanılabilir:

CREATE OR REPLACE RULE update_vehicle_view AS
  ON UPDATE TO vehicle
  DO INSTEAD
     ( 
      UPDATE private.vehicle
        SET make = NEW.make,
            model = NEW.model
      WHERE vehicle_id = OLD.vehicle_id
       AND (NEW.incident_id IN ( SELECT incident_id
                   FROM usr_incident));
     UPDATE private.driver
        SET driver_name = NEW.driver_name
       FROM private.vehicle v
      WHERE driver_id = v.driver_id
      AND vehicle_id = OLD.vehicle_id
      AND (NEW.incident_id IN ( SELECT incident_id
                   FROM usr_incident));               
   )

Bu korur current_user. RETURNINGYine de destekleyici hükümler biraz kıllı olabilir. Ayrıca, bir dizinin kullanımını işlemek için her iki tabloya aynı anda eklemek için kuralları kullanmanın güvenli bir yolunu bulamadım driver_id. En kolay yolu kullanmak olurdu WITHbir in maddesi INSERT(CTE), ancak bunlar ile bağlantılı olarak izin verilmez NEW(hata: rules cannot refer to NEW within WITH query) başvurmak ettirecek şekilde, lastval()hangi kesinlikle önerilmez .

Yanıtlar:


4

Peki, current_userdbuser grubuna şemadaki ilişkilere özel olarak doğrudan erişim izni vermeden korumanın bir yolu var mı?

INSTEAD OFGörünüm yoluyla yazma erişimi sağlamak için bir tetikleyici yerine bir kural kullanabilirsiniz . Görünümler her zaman sorgu yaratıcısı yerine görünüm oluşturucunun güvenlik haklarıyla hareket eder, ancak değişiklikleri düşünmüyorum current_user .

Uygulamanız doğrudan kullanıcı olarak bağlanıyorsa, session_userbunun yerine kontrol edebilirsiniz current_user. Bu aynı zamanda genel bir kullanıcıya bağlanırsanız da çalışır SET SESSION AUTHORIZATION. Yine de, genel bir kullanıcı olarak SET ROLEistediğiniz kullanıcıya bağlanırsanız çalışmaz .

Bir SECURITY DEFINERişlevden hemen önceki kullanıcıyı almanın bir yolu yoktur . Sadece current_userve alabilirsiniz session_user. last_userKullanıcı kimliklerini almanın veya bir yığınını almanın bir yolu iyi olurdu, ancak şu anda desteklenmemektedir.


Aha, daha önce kurallarla ilgilenmemişti, teşekkürler. SET SESSIONdaha iyi olabilir ama ilk giriş kullanıcısının tehlikeli kokulu süper kullanıcı ayrıcalıklarına sahip olması gerektiğini düşünüyorum.
beldaz

@beldaz Evet. Bu büyük sorun SET SESSION AUTHORIZATION. Gerçekten onun arasında bir şey istiyorum SET ROLE, ama şu anda böyle bir şey yok.
Craig Ringer

1

Tam bir cevap değil, ama bir yoruma uymuyor.

lastval() & currval()

lastval()Cesaret kırıldığını düşündüren nedir ? Bir yanlış anlama gibi görünüyor.

In başvurulan cevap Craig kuvvetle bir kuralın yerine tetiği kullanmayı önerir comment . Ve katılıyorum - özel durumunuz hariç, açıkçası.

Cevap kuvvetle kullanımını teşvik etmemektedir currval()- ama bu bir misundertstanding gibi görünüyor. Onunla ilgili yanlış bir şey yok lastval()ya da daha doğrusu currval(). Referans verilen cevapla bir yorum bıraktım.

Kılavuzdan alıntı:

currval

nextvalGeçerli oturumda bu sıra için en son elde edilen değeri döndürür . ( nextvalBu oturumda bu sıra için hiç çağrılmadıysa bir hata bildirilir .) Bu, oturum yerel değeri döndürdüğünden nextval, geçerli oturumdan bu yana başka oturumların yürütülüp yürütülmediği tahmin edilebilir bir yanıt verir .

Bu, eşzamanlı işlemlerde güvenlidir. Tek olası komplikasyon, aynı tetikleyiciyi yanlışlıkla çağırabilecek diğer tetikleyicilerden veya kurallardan kaynaklanabilir - bu çok olası bir senaryo olabilir ve hangi tetikleyicileri / kuralları yüklediğiniz üzerinde tam kontrole sahip olursunuz.

Ancak , komutların dizisinin kurallar dahilinde korunduğundan emin değilim ( currval()değişken bir işlev olsa da ). Ayrıca, çok sıralı bir INSERTsenkronizasyondan kurtulmanızı sağlayabilir. KURALınızı iki kurala bölebilirsiniz, sadece ikincisi INSTEAD. Unutmayın, belgelere göre:

Aynı tablodaki ve aynı olay türündeki birden çok kural alfabetik sıraya göre uygulanır.

Daha fazla araştırma yapmadım, zamanın dışında.

DEFAULT PRIVILEGES

Gelince:

SET SESSION AUTHORIZATION dbowner;
...
GRANT ALL ON TABLE private.incident TO dbview;

Bunun yerine ilginizi çekebilir:

ALTER DEFAULT PRIVILEGES FOR ROLE dbowner IN SCHEMA private
   GRANT ALL ON TABLES TO dbview;

İlişkili:


Teşekkürler, gerçekten benim anlayışım yanlıştı lastvalve currvalfarkında değilim, onlar bir oturum için yerel olduklarını. Aslında benim gerçek şememde varsayılan ayrıcalıkları kullanıyorum, ama tablo başına olanlar kopyalama ve döküm DB'den yapıştırma vardı. İlişkileri yeniden yapılandırmanın kurallarla uğraşmaktan daha kolay olduğu sonucuna vardım, temiz olsalar da, daha sonra baş ağrısı olduklarını görebiliyorum.
beldaz

@beldaz: Bence bu iyi bir karar. Tasarımınız çok karmaşıklaşıyordu.
Erwin Brandstetter
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.