[RUBY] Mandanten mit Rails unter Verwendung der Sicherheitsrichtlinie auf Zeilenebene von PostgreSQL

Ich mache ein SaaS namens Clipkit. Es handelt sich um ein System mit mehreren Mandanten und einer Methode mit mehreren Schemata. Da sich jedoch die Anzahl der Mandanten 1.000 nähert und es schwierig wird, überprüfen wir andere Methoden.

LD;TR

Ich habe versucht, eine mandantenfähige Implementierung mit der PostgreSQL-Sicherheitsrichtlinie auf Zeilenebene durchzuführen.

Ich konnte es ohne Probleme implementieren und es schien gut zu funktionieren. Am Ende vergaß ich jedoch, ihn diesmal einzustellen. Die mandantenfähige Methode von RDB hat Vor- und Nachteile und ist schwierig.

Persönlich dachte ich, es wäre besser, mit der Multi-Schema-Methode anstelle von RLS zu beginnen.

Einführung

In SaaS-Webdiensten gibt es eine Methode, bei der unabhängige Anwendungen für jeden Kunden in einem System koexistieren. Dies wird als mandantenfähig bezeichnet.

RDB-Mandantenmethode

Wenn Sie normal darüber nachdenken, können Sie sich zunächst ein Design vorstellen, das Daten von mehreren Mandanten in einer Tabelle mischt. Wenn das Programm jedoch einen Fehler enthält, kann dies zu einem sehr großen Sicherheitsproblem führen, z. B. wenn die Daten anderer Mandanten sichtbar sind.

Daher ist es notwendig, eine Methode zu überlegen, um die Daten für jeden Mieter zu trennen, damit es nicht zu einer Kreuzung kommt.

Es gibt ungefähr drei Möglichkeiten, mit RDB Mandantenfähigkeit zu erreichen.

Multi-Instanz (Silo)

Verwenden Sie für jeden Mandanten eine unabhängige DB-Instanz (virtuelle Maschine usw.). Hohe Unabhängigkeit, aber geringe Kosten und Wartbarkeitsvorteile.

Einzelinstanz-Multi-Schema (Bridge)

Bereiten Sie für jeden Mandanten in einer einzelnen DB-Instanz ein Schema vor. Da jeder Mandant über eine unabhängige Tabelle verfügt, ist die Verwaltung der Tabellendefinition kompliziert.

Einzelschema (Pool)

Mischen Sie Daten aller Mandanten in einer Tabelle in einem einzigen Schema. Es ist am ressourceneffizientesten, aber wenn das Programm einen Fehler enthält, besteht ein großes Risiko, dass die Daten anderer Mandanten gemischt werden.

Die Multi-Schema-Methode kann leicht realisiert werden

Rails hat ein Juwel namens Apartment. Damit erfolgt die gleichzeitige Migration zu allen Mandanten ohne Erlaubnis.

Nachteile der Multi-Schema-Methode

Durch Trennen der Schemas für jeden Mandanten muss beim Ändern der Tabellenstruktur für alle Schemas dieselbe Migration durchgeführt werden. Selbst wenn der Migrationsprozess 2 bis 3 Sekunden dauert, wird es schwieriger, wenn die Anzahl der Mandanten mehrere Tausend überschreitet. Es fallen auch hohe Verwaltungskosten an, um sicherzustellen, dass die Migration für alle Mandanten abgeschlossen ist.

Wenn die Zugriffskontrolle gewährleistet werden kann, halte ich die Einzelschemamethode daher für ideal.

Einzelschemamethode unter Verwendung der Sicherheitsrichtlinie auf Zeilenebene

Überblick

PostgeeSQL 9.5 und höher verfügt über eine Funktion namens RLS (Row Level Security Policy). Dies ist eine Funktion, die nur den Zugriff auf Zeilen mit vordefinierten Bedingungen ermöglicht, abhängig von der Rolle des Benutzers und den Laufzeitparametern.

Einstellmethode

Stellen Sie insbesondere Folgendes ein.

Beispiel) Ich möchte die Spalte tenant_id der Benutzertabelle mit Ausnahme von Datensätzen mit einem bestimmten Wert unsichtbar machen.

Stellen Sie RLS ein. (Dies ist eine Einstellung, die gemäß den Laufzeitparametern steuert.)

ALTER TABLE users ENABLE ROW LEVEL SECURITY;
CREATE POLICY user_isolation_policy ON users FOR ALL USING (tenant_id = current_setting('tenant.id')::BIGINT);

Wenn Sie danach die Laufzeitparameter wie folgt festlegen, können Sie nur noch auf Datensätze mit tenant_id = 999 zugreifen.

SET tenant.id = 999;

Implementierung in Rails (Entwurf)

Erstellen Sie eine Mandantentabelle (Mandantenmodell), die Mandanten verwaltet. (* Da dies ein Beispiel zur Erklärung ist, wird die Tabellendefinition weggelassen.)

Implementieren Sie dies so, dass Mandanten mit der Methode "Tenant # switch" gewechselt werden können. Es ist auch nützlich, den aktuellen Mandanten mit "Tenant.current" abrufen zu können.

class Tenant < ApplicationRecord
  def switch
    ActiveRecord::Base.connection.execute("SET tenant.id = #{id}")
  end
  def self.current
    find(ActiveRecord::Base.connection.execute('SHOW tenant.id').getvalue(0, 0))
  end
end

Nehmen Sie in before_action von Application Controller den Mandantenwechsel entsprechend der Domäne der Anforderung vor.

class ApplicationController < ActionController::API
  before_action :switch_tenant

  def switch_tenant
    Tenant.find_by(domain: request.host).switch
  end
end

Jetzt können Sie nur noch die Daten Ihres Mieters berühren.

Beim Hinzufügen von Daten müssen Sie jedoch die tenant_id selbst eingeben. Da dies problematisch ist, implementieren Sie es in der Basisklasse (ApplicationRecord) von Model, damit es automatisch eingegeben wird.

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  after_initialize :set_tenant_id

  def set_tenant_id
    if new_record?
      if has_attribute?(:tenant_id)
        self.tenant_id = Tenant.current.id
      end
    end
  end
end

Jetzt können Sie transparent auf Daten zugreifen, ohne den Mandanten zu kennen.

wichtiger Punkt

RLS funktioniert nur für allgemeine Benutzer

Die RLS-Einschränkung ist für Benutzer mit CREATE TABLE und SUPER USER ungültig. Die Migration wird also von SUPER USER ausgeführt und die Anwendung wird von einem allgemeinen Benutzer gestartet. Und so weiter.

Geben Sie den allgemeinen Benutzern die erforderlichen Berechtigungen wie folgt.

GRANT SELECT, UPDATE, INSERT, DELETE ON ALL TABLES IN SCHEMA public TO PUBLIC;
GRANT SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO PUBLIC;

Beachten Sie die UNIQUE-Einschränkung

Die UNIQUE-Einschränkung muss ein zusammengesetzter Index mit tenant_id sein. (Da es für die Anwendung nicht sichtbar ist, muss keine zusammengesetzte Bedingung für die Validierung festgelegt werden.)

Vorsichtsmaßnahmen beim EINFÜGEN

SELECT sieht nur transparent eingeschränkte Datensätze, aber Sie müssen tenant_id selbst festlegen, wenn Sie INSERT ausführen. (Im obigen Implementierungsplan wurde er automatisch mit der Modellbasisklasse "ApplicationRecord" eingegeben.)

Migrationshinweise

Die beim Hinzufügen einer Tabelle erforderliche CREATE POLICY soll durch Migration erfolgen. In diesem Fall wird sie jedoch nicht in schema.rb wiedergegeben, sodass "db: reset" / "db: setup" nicht verwendet werden kann. (Db: migrate: reset ist ok)

Fehler

Dieses Mal habe ich es mit dieser Implementierung nicht wirklich betrieben, aber ich werde die möglichen Nachteile ansprechen.

Der Tisch wächst

RDB wird schwierig zu handhaben, wenn die Anzahl der Datensätze sehr groß wird. Selbst wenn der Index gesetzt ist, gelangt er nicht in den Speicher und wird plötzlich schwer.

Daher werden wir die Verwendung der Partitionierungsfunktion (Tabellenpartitionierung) in Betracht ziehen. Es gibt eine Methode namens Listenpartition, die sich nach dem Wert der Spalte teilt. Sie werden diese also wahrscheinlich verwenden.

Teilen Sie die Tabelle für jede tenant_id. Ich habe zuerst die Strategie entwickelt, aber im Allgemeinen scheint es nicht zu erwarten, dass eine untergeordnete Tabelle erstellt wird, deren Partitionierung 100 überschreitet, und es gibt Berichte, dass Leistungsprobleme auftreten (versuchen Sie es tatsächlich). Ich nicht). Dieser Ansatz scheint nicht sehr realistisch.

Es scheint, dass die Strategie darin bestehen wird, sie manuell zu teilen, wenn die Daten zunehmen. Es ist nervig.

Probleme beim Löschen von Mandanten

Es scheint schwierig zu sein, weil ich die Datensätze aller Tabellen löschen muss. Bei der Multi-Schema-Methode war dies einfach, da Sie nur das Schema gelöscht haben.

Es ist schwierig, Daten aus anderen Umgebungen zu migrieren

Obwohl es sich um einen SaaS-Dienst handelt, wird er auch lokal bereitgestellt. In einem solchen Fall scheint es schwierig zu sein, Daten von lokal zu SaaS zu migrieren. Dies liegt daran, dass sich die ID jeder Tabelle ändert. Bei der Multi-Schema-Methode müssen Sie nur sichern und wiederherstellen.

RLS beschloss, darauf zu verzichten

Immerhin scheint der aufgeblähte Tisch schmerzhaft zu sein. Ich konnte meine Sorge nicht loswerden.

Die Schwierigkeit der Migration mit der Multi-Schema-Methode ist nur bei der Bereitstellung ein Problem, und ich bin der Meinung, dass dies viel besser ist, als sich täglich um die Leistung zu sorgen.

Bei der Migration ist es fast kein Problem, wenn die Anzahl der Mandanten mehrere Hundert beträgt. Abhängig von der Anzahl der Mandanten kann die Multi-Schema-Methode in der Startphase verwendet werden. Ich dachte.

Apartment verfügt außerdem über eine Funktion, mit der Sie den DB-Server je nach Mandant ändern können, sodass die Leistung sicherer ist.

Andere Lösungen

Citus https://www.citusdata.com/

Eine PostgreSQL-Erweiterung, mit der sich mehrere Mandanten wohl fühlen.

Da es sich um OSS handelt, kann es auf EC2 installiert werden, aber nicht auf RDS ...

Es scheint, dass es einen Dienst namens Citus Cloud gab, der ab 2016 verwaltete Dienste auf AWS bereitstellt.

Im Jahr 2019 erwarb Microsoft jedoch Citus. Citus Cloud ist geschlossen. Es scheint, dass es stattdessen in Azure verwendet werden kann. Ah "~

AWS ist ein Muss, also ist es schwierig ...

Die Wohnungsentwicklung stagniert

Okay, ich werde weiterhin mit der Wohnung gehen! Ich habe versucht, es in einem neuen Projekt (Rails 6) zu verwenden, aber es hat nicht funktioniert.

Ich denke, es ist ein ziemlich großes Juwel, aber zu diesem Zeitpunkt (Juli 2020) war es noch nicht mit Rails 6 kompatibel.

Es gab eine Fork-Version, die aktiv gewartet wurde, daher scheint es in Ordnung zu sein, diese vorerst zu verwenden.

Recommended Posts

Mandanten mit Rails unter Verwendung der Sicherheitsrichtlinie auf Zeilenebene von PostgreSQL
Japanisieren Sie mit i18n mit Rails
Hinweise zur Verwendung von FCM mit Ruby on Rails