Datenbank-Sicherheit#
Datenbank-Berechtigungen#
Das PostgreSQL-Login per Superuser postgres
sollte immer nur über
Unix-Domain-Sockets und über localhost
erlaubt sein. Der Zugang mit
Peer-Authentifizierung in der
pg_hba.conf-Datei kann
hingegen gewährt werden:
# TYPE DATABASE USER ADDRESS METHOD
local all postgres peer
host all all 10.23.42.1/24 scram-sha-256
Die Datenbank sollte vom Datenbankadministrator angelegt und anschließend so
konfiguriert werden, dass sich nicht jeder (PUBLIC)
damit verbinden kann:
CREATE DATABASE myapp;
REVOKE ALL ON myapp FROM PUBLIC;
Damit kann sich nur noch der Superuser mit der Datenbank myapp
verbinden.
Passwörter speichern#
Passwörter sollten niemals im Klartext, also z.B. auch nicht in einer
.env
-Datei gespeichert werden. Beim Speichern und Übermitteln von
Passwörtern sollte dies immer mit Salts versehen sein. Für
PostgreSQL gibt es hierfür die Erweiterung pgcrypto, die
einfach aktiviert werden kann mit
CREATE EXTENSION pgcrypto;
Daher sollten bereits beim Anlegen sichere Passwörter vergeben werden, die anschließend z.B. in Vault o.ä. gespeichert werden:
CREATE ROLE myapp_users;
CREATE ROLE myapp_reader IN ROLE myapp_users LOGIN PASSWORD '…';
CREATE ROLE myapp_writer IN ROLE myapp_users LOGIN PASSWORD '…';
Anschließend erhalten dann User mit der Rolle myapp_users
zunächst
CONNECT
-Rechte und dann myapp_reader
Leserechte und myapp_writer
Schreibrechte:
GRANT CONNECT ON DATABASE to myapp_users;
GRANT SELECT ON diagnosis_key TO myapp_reader;
GRANT INSERT ON diagnosis_key TO myapp_writer;
Der User myapp_reader
kann damit jedoch alle Daten auf einmal lesen. Auch
dies ist ein Angriffspunkt, der besser durch eine Funktion beschnitten wird:
CREATE OR REPLACE FUNCTION get_key_data(in_id UUID)
RETURNS JSONB
AS 'SELECT key_data FROM diagnosis_key WHERE id = in_id;'
LANGUAGE sql SECURITY DEFINER SET search_path = :schema, pg_temp;
Anschließend wird die Funktion myapp_owner
zugewiesen, myapp_reader
und
myapp_writer
die Berechtigungen entzogen und schließlich die Ausführung der
Funktion myapp_reader
erlaubt:
ALTER FUNCTION get_key_data(UUID) OWNER TO myapp_owner;
REVOKE ALL ON FUNCTION get_key_dataUUID) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION get_key_data(UUID) TO myapp_reader;
Damit kann myapp_reader
also nur noch einen einzelnen Datensatz lesen.
id#
Die id
sollte nicht als serial
, bigserial
o.ä. realisiert werden.
Hochzählende Zahlen könnten von Angreifern leicht erraten werden. Daher ist der
UUIDv4-Datentyp deutlich besser geeignet. In PostgreSQL könnt ihr UUIDv4
generieren mit der uuid-ossp-Erweiterung oder für
PostgreSQL≥9.4 auch der pgcrypto-Erweiterung:
CREATE EXTENSION "uuid-ossp";
CREATE TABLE diagnosis_key (
id uuid primary key default uuid_generate_v4() NOT NULL,
...
);
oder
CREATE EXTENSION "pgcrypto";
CREATE TABLE diagnosis_key (
id uuid primary key default gen_random_uuid() NOT NULL,
...
);
Zeitstempel#
Gelegentlich werden Datum und Zeit als bigint
, also als Zahl, gespeichert,
und dies obwohl es auch einen TIMESTAMP
-Datentyp gibt. Dies hätte den
Vorteil, dass dann auch einfach mit ihnen gerechnet werden kann, also z.B.:
SELECT age(submission_timestamp);
SELECT submission_timestamp - '1 day'::interval;
Außerdem könnten die Daten nach einer bestimmten Zeit gelöscht werden, z.B. nach dreißig Tagen mit:
DELETE FROM diagnosis_key WHERE age(submission_timestamp) > 30;
Das Löschen kann noch beschleunigt werden, wenn für jeden Tag mit der PostgreSQL-Erweiterung pg_partman eine eigene Partition erstellt wird.