From ba86ae504d8ea9796e43c1b061aa070761cd1323 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Mon, 21 Nov 2022 18:58:56 +0100 Subject: pgbackrest --- hosts/surtr/postgresql/default.nix | 239 ++++++++++++++++++++++++++++++++++ hosts/surtr/postgresql/pgbackrest.crt | 13 ++ hosts/surtr/postgresql/pgbackrest.key | 26 ++++ 3 files changed, 278 insertions(+) create mode 100644 hosts/surtr/postgresql/default.nix create mode 100644 hosts/surtr/postgresql/pgbackrest.crt create mode 100644 hosts/surtr/postgresql/pgbackrest.key (limited to 'hosts/surtr/postgresql') diff --git a/hosts/surtr/postgresql/default.nix b/hosts/surtr/postgresql/default.nix new file mode 100644 index 00000000..9cf494ae --- /dev/null +++ b/hosts/surtr/postgresql/default.nix @@ -0,0 +1,239 @@ +{ pkgs, sources, config, flake, ... }: +let + versioning = sources.psql-versioning.src; +in { + config = { + services.postgresql = { + enable = true; + package = pkgs.postgresql_14; + }; + + services.pgbackrest = { + enable = true; + settings = { + "surtr" = { + pg1-path = config.services.postgresql.dataDir; + + repo1-path = "/var/lib/pgbackrest"; + repo1-retention-full-type = "time"; + repo1-retention-full = 7; + repo1-retention-archive = 2; + + repo2-host-type = "tls"; + repo2-host = "pgbackrest.vidhar.yggdrasil"; + repo2-host-ca-file = toString ../../vidhar/pgbackrest/ca/ca.crt; + repo2-host-cert-file = toString ./pgbackrest.crt; + repo2-host-key-file = config.sops.secrets."pgbackrest.key".path; + repo2-retention-full-type = "time"; + repo2-retention-full = 14; + repo2-retention-archive = 7; + }; + + "global" = { + compress-type = "zst"; + compress-level = 9; + + archive-async = true; + spool-path = "/var/spool/pgbackrest"; + }; + + "global:server" = { + tls-server-address = "2a03:4000:52:ada:1::"; + tls-server-ca-file = toString ../../vidhar/pgbackrest/ca/ca.crt; + tls-server-cert-file = toString ./pgbackrest.crt; + tls-server-key-file = config.sops.secrets."pgbackrest.key".path; + tls-server-auth = ["vidhar.yggdrasil=surtr"]; + }; + + "global:archive-push" = { + process-max = 2; + }; + "global:archive-get" = { + process-max = 2; + }; + }; + + tlsServer.enable = true; + + backups."surtr-daily" = { + stanza = "surtr"; + repo = "1"; + timerConfig.OnCalendar = "daily"; + }; + }; + + sops.secrets."pgbackrest.key" = { + format = "binary"; + sopsFile = ./pgbackrest.key; + owner = "postgres"; + group = "postgres"; + mode = "0400"; + }; + + systemd.tmpfiles.rules = [ + "d /var/lib/pgbackrest 0750 postgres postgres - -" + "d /var/spool/pgbackrest 0750 postgres postgres - -" + ]; + + systemd.services.migrate-postgresql = { + after = [ "postgresql.service" ]; + bindsTo = [ "postgresql.service" ]; + wantedBy = [ "postgresql.service" ]; + + serviceConfig = { + Type = "oneshot"; + inherit (config.systemd.services.postgresql.serviceConfig) User Group; + RemainAfterExit = true; + }; + + path = [ config.services.postgresql.package ]; + script = '' + psql postgres postgres -eXf ${pkgs.writeText "schema.sql" '' + CREATE DATABASE "matrix-synapse" WITH TEMPLATE "template0" ENCODING "UTF8" LOCALE "C"; + CREATE DATABASE "email" WITH TEMPLATE "template0" ENCODING "UTF8" LOCALE "C"; + CREATE DATABASE "etebase" WITH TEMPLATE "template0" ENCODING "UTF8" LOCALE "C"; + ''} + + psql matrix-synapse postgres -eXf ${pkgs.writeText "matrix-synapse.sql" '' + \i ${versioning + "/install.versioning.sql"} + + BEGIN; + SELECT _v.register_patch('000-matrix-users', null, null); + + CREATE USER "matrix-synapse"; + GRANT ALL PRIVILEGES ON DATABASE "matrix-synapse" TO "matrix-synapse"; + GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "matrix-synapse"; + COMMIT; + ''} + + psql email postgres -eXf ${pkgs.writeText "email.sql" '' + \i ${versioning + "/install.versioning.sql"} + + BEGIN; + SELECT _v.register_patch('000-users', null, null); + + CREATE USER "postfix"; + GRANT CONNECT ON DATABASE "email" TO "postfix"; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO "postfix"; + CREATE USER "dovecot2"; + GRANT CONNECT ON DATABASE "email" TO "dovecot2"; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO "dovecot2"; + COMMIT; + + BEGIN; + SELECT _v.register_patch('001-spm', null, null); + + CREATE USER "spm"; + GRANT CONNECT ON DATABASE "email" TO "spm"; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES to "spm"; + COMMIT; + + BEGIN; + SELECT _v.register_patch('000-base', null, null); + + CREATE TABLE mailbox ( + id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(), + mailbox text NOT NULL CONSTRAINT mailbox_non_empty CHECK (mailbox <> '''), + quota_bytes bigint CONSTRAINT quota_bytes_positive CHECK (CASE WHEN quota_bytes IS NOT NULL THEN quota_bytes > 0 ELSE true END), + CONSTRAINT mailbox_unique UNIQUE (mailbox) + ); + CREATE TABLE mailbox_mapping ( + id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(), + local text CONSTRAINT local_non_empty CHECK (local IS DISTINCT FROM '''), + domain text NOT NULL CONSTRAINT domain_non_empty CHECK (domain <> '''), + mailbox uuid REFERENCES mailbox(id), + CONSTRAINT local_domain_unique UNIQUE (local, domain) + ); + CREATE UNIQUE INDEX domain_unique ON mailbox_mapping (domain) WHERE local IS NULL; + + CREATE VIEW virtual_mailbox_domain (domain) AS SELECT DISTINCT domain FROM mailbox_mapping; + CREATE VIEW virtual_mailbox_mapping (mailbox, lookup) AS SELECT mailbox.mailbox as mailbox, (CASE WHEN local IS NULL THEN ''' ELSE local END) || '@' || domain AS lookup FROM mailbox_mapping INNER JOIN mailbox on mailbox.id = mailbox_mapping.mailbox; + + CREATE VIEW imap_user ("user", quota_rule) AS SELECT mailbox AS "user", (CASE WHEN quota_bytes IS NULL THEN '*:ignore' ELSE '*:bytes=' || quota_bytes END) AS quota_rule FROM mailbox; + COMMIT; + + BEGIN; + SELECT _v.register_patch('001-lmtp-mapping', ARRAY['000-base'], null); + + CREATE VIEW lmtp_mapping ("user", quota_rule, local, domain) AS SELECT mailbox.mailbox AS "user", (CASE WHEN quota_bytes IS NULL THEN '*:ignore' ELSE '*:bytes=' || quota_bytes END) AS quota_rule, local, domain FROM mailbox INNER JOIN mailbox_mapping ON mailbox.id = mailbox_mapping.mailbox; + COMMIT; + + BEGIN; + SELECT _v.register_patch('002-citext', ARRAY['000-base'], null); + + DROP VIEW virtual_mailbox_domain; + DROP VIEW virtual_mailbox_mapping; + DROP VIEW imap_user; + DROP VIEW lmtp_mapping; + + CREATE EXTENSION citext; + + ALTER TABLE mailbox ALTER mailbox TYPE citext; + ALTER TABLE mailbox_mapping ALTER local TYPE citext; + ALTER TABLE mailbox_mapping ALTER domain TYPE citext; + + CREATE VIEW mailbox_quota_rule (id, mailbox, quota_rule) AS SELECT id, mailbox, (CASE WHEN quota_bytes IS NULL THEN '*:ignore' ELSE '*:bytes=' || quota_bytes END) AS quota_rule FROM mailbox; + + CREATE VIEW virtual_mailbox_domain (domain) AS SELECT DISTINCT domain FROM mailbox_mapping; + CREATE VIEW virtual_mailbox_mapping (lookup) AS SELECT (CASE WHEN local IS NULL THEN ''' ELSE local END) || '@' || domain AS lookup FROM mailbox_mapping; + CREATE VIEW imap_user ("user", quota_rule) AS SELECT mailbox AS "user", quota_rule FROM mailbox_quota_rule; + CREATE VIEW lmtp_mapping ("user", quota_rule, local, domain) AS SELECT mailbox_quota_rule.mailbox AS "user", quota_rule, local, domain FROM mailbox_quota_rule INNER JOIN mailbox_mapping ON mailbox_quota_rule.id = mailbox_mapping.mailbox; + COMMIT; + + BEGIN; + SELECT _v.register_patch('003-extensions', ARRAY['000-base', '002-citext'], null); + + ALTER TABLE mailbox_mapping ADD COLUMN extension citext CHECK (CASE WHEN extension IS NOT NULL THEN extension NOT LIKE '%+%' ELSE true END); + + DROP VIEW virtual_mailbox_mapping; + DROP VIEW lmtp_mapping; + + CREATE VIEW virtual_mailbox_mapping (lookup) AS SELECT (CASE WHEN local IS NULL THEN ''' ELSE local END) || (CASE WHEN extension IS NULL THEN ''' ELSE '+' || extension END) || '@' || domain AS lookup FROM mailbox_mapping WHERE mailbox IS NOT NULL; + CREATE VIEW virtual_mailbox_access (lookup, action) AS SELECT (CASE WHEN local IS NULL THEN ''' ELSE local END) || (CASE WHEN extension IS NULL THEN ''' ELSE '+' || extension END) || '@' || domain AS lookup, CASE WHEN mailbox IS NULL THEN 'REJECT' ELSE 'DUNNO' END AS action FROM mailbox_mapping; + CREATE VIEW lmtp_mapping ("user", quota_rule, local, extension, domain) AS SELECT mailbox_quota_rule.mailbox AS "user", quota_rule, local, extension, domain FROM mailbox_quota_rule INNER JOIN mailbox_mapping ON mailbox_quota_rule.id = mailbox_mapping.mailbox; + COMMIT; + + BEGIN; + SELECT _v.register_patch('004-cascade', ARRAY['000-base'], null); + + ALTER TABLE mailbox_mapping DROP CONSTRAINT mailbox_mapping_mailbox_fkey; + ALTER TABLE mailbox_mapping ADD CONSTRAINT mailbox_mapping_mailbox_fkey FOREIGN KEY (mailbox) REFERENCES mailbox(id) ON DELETE CASCADE ON UPDATE RESTRICT; + COMMIT; + + BEGIN; + SELECT _v.register_patch('005-spm', ARRAY['000-base', '002-citext', '003-extensions'], null); + + GRANT INSERT ON "mailbox_mapping" TO "spm"; + COMMIT; + + BEGIN; + SELECT _v.register_patch('006-spm-mailbox', ARRAY['000-base'], null); + + GRANT SELECT ON ALL TABLES IN SCHEMA public TO "spm"; + COMMIT; + + BEGIN; + SELECT _v.register_patch('007-ccert-sender-policy', ARRAY['000-base'], null); + + CREATE USER "postfix-ccert-sender-policy"; + GRANT CONNECT ON DATABASE "email" TO "postfix-ccert-sender-policy"; + ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO "postfix-ccert-sender-policy"; + GRANT SELECT ON ALL TABLES IN SCHEMA public TO "postfix-ccert-sender-policy"; + COMMIT; + ''} + + psql etebase postgres -eXf ${pkgs.writeText "etebase.sql" '' + \i ${versioning + "/install.versioning.sql"} + + BEGIN; + SELECT _v.register_patch('000-user', null, null); + + CREATE USER "etebase"; + GRANT ALL PRIVILEGES ON DATABASE "etebase" TO "etebase"; + GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "etebase"; + COMMIT; + ''} + ''; + }; + }; +} diff --git a/hosts/surtr/postgresql/pgbackrest.crt b/hosts/surtr/postgresql/pgbackrest.crt new file mode 100644 index 00000000..b4dc4d97 --- /dev/null +++ b/hosts/surtr/postgresql/pgbackrest.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB7zCCAW+gAwIBAgIPQAAAAGN7p/Q5SZ7JU43JMAUGAytlcTAfMR0wGwYDVQQD +DBRwZ2JhY2tyZXN0LnlnZ2RyYXNpbDAeFw0yMjExMjExNjI2MTFaFw0zMjExMjEx +NjMxMTFaMBoxGDAWBgNVBAMMD3N1cnRyLnlnZ2RyYXNpbDAqMAUGAytlcAMhABIl +okEGkov33jgsrF0QA4CKQILbIWkZ2tn+UUhXxxyDo4HGMIHDMB8GA1UdIwQYMBaA +FO+/yfEkwcLr+vNPIsyCW86UwJ3aMB0GA1UdDgQWBBQnVeShLYsqF35OmmzLJEV5 +dfenhjAOBgNVHQ8BAf8EBAMCBeAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggr +BgEFBQcDAgYIKwYBBQUHAwEwRAYDVR0RBD0wO4IdcGdiYWNrcmVzdC5zdXJ0ci55 +Z2dkcmFzaWwubGmCGnBnYmFja3Jlc3Quc3VydHIueWdnZHJhc2lsMAUGAytlcQNz +AJqqMDWN1Ym5XANRKWcCh09j0Rej3V64XZlOOP7qFF9Gh4QJXeCvDMjX4LOeRUmi +lB8iosdRN9MSANI4kfwYBnzgn3BNMrvMI4faEOuVnd6X2ulsJdNbJNQzB3hRVsNf +b+QNBV+PpTUgR4k9e1XWX+wwAA== +-----END CERTIFICATE----- diff --git a/hosts/surtr/postgresql/pgbackrest.key b/hosts/surtr/postgresql/pgbackrest.key new file mode 100644 index 00000000..bc2af12d --- /dev/null +++ b/hosts/surtr/postgresql/pgbackrest.key @@ -0,0 +1,26 @@ +{ + "data": "ENC[AES256_GCM,data:Bg4fIAqIGLF1P1P583vQnHhjzrD8fdnS5tA/7SuSdBRJjVaRzB0bieEv+2i9WxgaStG9TTUSmClCVUsbR5gy7MoV6Br4AL17Y++R6wPpJbQJvtMMDJB2xg+THU/Ex61dendcWqPYh73Wn4U9uBE/wC1eVrShXRM=,iv:YG/foZwVcrzi6hdk7Vk0sYZ92LMbmiKg1SbAgPaeUNM=,tag:lAcoxUfQXB4vvc6XnIcA/g==,type:str]", + "sops": { + "kms": null, + "gcp_kms": null, + "azure_kv": null, + "hc_vault": null, + "age": null, + "lastmodified": "2022-11-21T14:30:27Z", + "mac": "ENC[AES256_GCM,data:Dsfc1XrGl4abSnDqRl/IwC11bVy+kHz1RaI0V/nkkaJ3fM/qTXPVc5mMoWCiPn1nz5BTABQRSnrf79qHc0wpZ1WUpn07yOf7JejJ/T/bUC7D8BuoVdWRh1og+NzWCEIwaGXg0Eo04yli+GXisdM3YVM9g3BrxYrSInjnNZFyB+Q=,iv:T5QprwIhB8ZWwmmfWVtxkXqbMB1onW+wX7GPIFMn+z0=,tag:zMi77nMepajhg2Djgz8rBA==,type:str]", + "pgp": [ + { + "created_at": "2022-11-21T14:30:27Z", + "enc": "-----BEGIN PGP MESSAGE-----\n\nhF4DXxoViZlp6dISAQdAi3pfg9DA+1v5r5sEijbkdwmOopWh05IuhRJxuy1btyAw\nuo0iV7VpngK8tFcBHnmhx3QsxIJo/gU+xrOwczW3RoSGrWo9tV2FantQPRp6f1aS\n0lwBEJSxmTApD/YDu3M6WhxN49/ZVEXG+KQ/mOdoBo0ITGKa6No0btMolzJ0bCJU\n+/avVdlDdZzfXo9XP0iJUoqh+1yMn+XdnD5deGac8a/QGvXZkxsYQ8KpK9sONA==\n=QyKr\n-----END PGP MESSAGE-----\n", + "fp": "30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51" + }, + { + "created_at": "2022-11-21T14:30:27Z", + "enc": "-----BEGIN PGP MESSAGE-----\n\nhF4DyFKFNkTVG5oSAQdAYU2U/anEJ8JSiG7NBppmsFeogXN3ynOEdq2tHXf+mUww\nIS7kW1pqcGMjnf7RQNuL91Wek5GEk4T498IFadiYDImAfIdS5jeX2w7UvxWLX5OZ\n0lwBlnxOwkYRWZzAhB6jHthmk2zEc+0JKuFolXhrwXqsFwFGoLTO9fctJrV7ry0u\naM9DqXru+/cEUZJDSq5GYDQaxTjyaFMVwLVdfxrtFwc8YMlqU8vVoWTqLaUVYA==\n=Tg80\n-----END PGP MESSAGE-----\n", + "fp": "7ED22F4AA7BB55728B643DC5471B7D88E4EF66F8" + } + ], + "unencrypted_suffix": "_unencrypted", + "version": "3.7.3" + } +} \ No newline at end of file -- cgit v1.2.3