Преглед изворни кода

BREAKING feat(dbmaster,nsmaster): migrate from MariaDB to Postgres

When deployed from this commit as HEAD, nsmaster migrates its
database from MariaDB (dbmaster-legacy) to Postgres (dbmaster), and
then starts start the nameserver daemon. If the nameserver daemon
errors, the container is not restarted.
Peter Thomassen пре 3 година
родитељ
комит
a2c259d835

+ 1 - 1
README.md

@@ -6,7 +6,7 @@ This is a docker-compose application providing the basic stack for deSEC name se
 - `nslord`: Eventually authoritative DNS server (PowerDNS). DNSSEC keying material is generated here.
 - `nsmaster`: Stealth authoritative DNS server (PowerDNS). Receives fully signed AXFR zone transfers from `nslord`. No access to keys.
 - `api`: RESTful API to create deSEC users and domains, see [documentation](https://desec.readthedocs.io/).
-- `dbapi`, `dblord`, `dbmaster`: Postgres database for `api`, MariaDB databases for `nslord` and `nsmaster`, respectively.
+- `dbapi`, `dblord`, `dbmaster`: Postgres databases for `api` and `nsmaster`, MariaDB database for `nslord`, respectively.
 - `www`: nginx instance serving static web site content and proxying to `api`
 - `celery`: A shadow instance of the `api` code for performing asynchronous tasks (email delivery).
 - `rabbitmq`: `celery`'s queue

+ 0 - 0
dbmaster/51-server.cnf → dbmaster-legacy/51-server.cnf


+ 16 - 0
dbmaster-legacy/Dockerfile

@@ -0,0 +1,16 @@
+FROM mariadb:10.3
+
+# Use random throw-away root password. Our init scripts switch authentication to socket logins only
+ENV MYSQL_RANDOM_ROOT_PASSWORD=yes
+
+# install tools used in init script
+RUN set -ex && apt-get update && apt-get -y install gettext-base && apt-get clean && rm -rf /var/lib/apt/lists/*
+
+COPY initdb.d/* /docker-entrypoint-initdb.d/
+RUN chown -R mysql:mysql /docker-entrypoint-initdb.d/
+
+# Additional configuration
+COPY ./51-server.cnf /etc/mysql/conf.d/51-server.cnf
+
+# mountable storage
+VOLUME /var/lib/mysql

+ 0 - 0
dbmaster/initdb.d/00-init.sh → dbmaster-legacy/initdb.d/00-init.sh


+ 0 - 0
dbmaster/initdb.d/00-init.sql → dbmaster-legacy/initdb.d/00-init.sql


+ 0 - 0
dbmaster/initdb.d/00-init.sql.var → dbmaster-legacy/initdb.d/00-init.sql.var


+ 0 - 0
dbmaster/initdb.d/10-pdns-master.sql → dbmaster-legacy/initdb.d/10-pdns-master.sql


+ 0 - 0
dbmaster/initdb.d/11-pdns-master-REFERENCES.sql → dbmaster-legacy/initdb.d/11-pdns-master-REFERENCES.sql


+ 0 - 0
dbmaster/initdb.d/99-finish.sql → dbmaster-legacy/initdb.d/99-finish.sql


+ 9 - 11
dbmaster/Dockerfile

@@ -1,16 +1,14 @@
-FROM mariadb:10.3
+FROM postgres:13-alpine
 
-# Use random throw-away root password. Our init scripts switch authentication to socket logins only
-ENV MYSQL_RANDOM_ROOT_PASSWORD=yes
+RUN apk add --no-cache pwgen
 
-# install tools used in init script
-RUN set -ex && apt-get update && apt-get -y install gettext-base && apt-get clean && rm -rf /var/lib/apt/lists/*
+ADD docker-entrypoint-initdb.d /docker-entrypoint-initdb.d
 
-COPY initdb.d/* /docker-entrypoint-initdb.d/
-RUN chown -R mysql:mysql /docker-entrypoint-initdb.d/
-
-# Additional configuration
-COPY ./51-server.cnf /etc/mysql/conf.d/51-server.cnf
+USER postgres
 
 # mountable storage
-VOLUME /var/lib/mysql
+VOLUME /var/lib/postgresql/data
+
+COPY entrypoint-wrapper.sh /usr/local/bin/
+ENTRYPOINT ["entrypoint-wrapper.sh"]
+CMD ["postgres"]

+ 16 - 0
dbmaster/docker-entrypoint-initdb.d/init-user-db.sh

@@ -0,0 +1,16 @@
+#!/bin/bash
+set -e
+
+# Get the postgres user or set it to a default value
+if [ -n $POSTGRES_USER ]; then pg_user=$POSTGRES_USER; else pg_user="postgres"; fi
+# Get the postgres db or set it to a default value
+if [ -n $POSTGRES_DB ]; then pg_db=$POSTGRES_DB; else pg_db=$POSTGRES_USER; fi
+
+if [ -n "$POSTGRES_NON_ROOT_USER" ]; then
+psql -v ON_ERROR_STOP=1 --username "$pg_user" --dbname "$pg_db" <<-EOSQL
+    CREATE USER $POSTGRES_NON_ROOT_USER with encrypted password '$POSTGRES_NON_ROOT_USER_PASSWORD';
+    GRANT CREATE, CONNECT ON DATABASE $pg_db TO $POSTGRES_NON_ROOT_USER;
+    ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, UPDATE, INSERT, DELETE, REFERENCES ON TABLES TO $POSTGRES_NON_ROOT_USER;
+EOSQL
+psql -v ON_ERROR_STOP=1 --username "$POSTGRES_NON_ROOT_USER" --dbname "$pg_db" < /docker-entrypoint-initdb.d/schema.pgsql.sql.txt
+fi

+ 95 - 0
dbmaster/docker-entrypoint-initdb.d/schema.pgsql.sql.txt

@@ -0,0 +1,95 @@
+CREATE TABLE domains (
+  id                    SERIAL PRIMARY KEY,
+  name                  VARCHAR(255) NOT NULL,
+  master                VARCHAR(128) DEFAULT NULL,
+  last_check            INT DEFAULT NULL,
+  type                  VARCHAR(6) NOT NULL,
+  notified_serial       BIGINT DEFAULT NULL,
+  account               VARCHAR(40) DEFAULT NULL,
+  CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
+);
+
+CREATE UNIQUE INDEX name_index ON domains(name);
+
+
+CREATE TABLE records (
+  id                    BIGSERIAL PRIMARY KEY,
+  domain_id             INT DEFAULT NULL,
+  name                  VARCHAR(255) DEFAULT NULL,
+  type                  VARCHAR(10) DEFAULT NULL,
+  content               VARCHAR(65535) DEFAULT NULL,
+  ttl                   INT DEFAULT NULL,
+  prio                  INT DEFAULT NULL,
+  disabled              BOOL DEFAULT 'f',
+  ordername             VARCHAR(255),
+  auth                  BOOL DEFAULT 't',
+  CONSTRAINT domain_exists
+  FOREIGN KEY(domain_id) REFERENCES domains(id)
+  ON DELETE CASCADE,
+  CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
+);
+
+CREATE INDEX rec_name_index ON records(name);
+CREATE INDEX nametype_index ON records(name,type);
+CREATE INDEX domain_id ON records(domain_id);
+CREATE INDEX recordorder ON records (domain_id, ordername text_pattern_ops);
+
+
+CREATE TABLE supermasters (
+  ip                    INET NOT NULL,
+  nameserver            VARCHAR(255) NOT NULL,
+  account               VARCHAR(40) NOT NULL,
+  PRIMARY KEY(ip, nameserver)
+);
+
+
+CREATE TABLE comments (
+  id                    SERIAL PRIMARY KEY,
+  domain_id             INT NOT NULL,
+  name                  VARCHAR(255) NOT NULL,
+  type                  VARCHAR(10) NOT NULL,
+  modified_at           INT NOT NULL,
+  account               VARCHAR(40) DEFAULT NULL,
+  comment               VARCHAR(65535) NOT NULL,
+  CONSTRAINT domain_exists
+  FOREIGN KEY(domain_id) REFERENCES domains(id)
+  ON DELETE CASCADE,
+  CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
+);
+
+CREATE INDEX comments_domain_id_idx ON comments (domain_id);
+CREATE INDEX comments_name_type_idx ON comments (name, type);
+CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);
+
+
+CREATE TABLE domainmetadata (
+  id                    SERIAL PRIMARY KEY,
+  domain_id             INT REFERENCES domains(id) ON DELETE CASCADE,
+  kind                  VARCHAR(32),
+  content               TEXT
+);
+
+CREATE INDEX domainidmetaindex ON domainmetadata(domain_id);
+
+
+CREATE TABLE cryptokeys (
+  id                    SERIAL PRIMARY KEY,
+  domain_id             INT REFERENCES domains(id) ON DELETE CASCADE,
+  flags                 INT NOT NULL,
+  active                BOOL,
+  published             BOOL DEFAULT TRUE,
+  content               TEXT
+);
+
+CREATE INDEX domainidindex ON cryptokeys(domain_id);
+
+
+CREATE TABLE tsigkeys (
+  id                    SERIAL PRIMARY KEY,
+  name                  VARCHAR(255),
+  algorithm             VARCHAR(50),
+  secret                VARCHAR(255),
+  CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
+);
+
+CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);

+ 6 - 0
dbmaster/entrypoint-wrapper.sh

@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+set -Eeo pipefail
+
+# This password is set for the postgres user when initializing the database. It is not needed and thus not printed.
+export POSTGRES_PASSWORD=$(pwgen -1 -s 32)
+/usr/local/bin/docker-entrypoint.sh "$@"

+ 16 - 0
dbmaster/pg_hba.conf

@@ -0,0 +1,16 @@
+# TYPE  DATABASE        USER            ADDRESS                 METHOD
+
+# "local" is for Unix domain socket connections only
+local   all             all                                     trust
+# IPv4 local connections:
+#host    all             all             127.0.0.1/32            scram-sha-256
+# IPv6 local connections:
+#host    all             all             ::1/128                 scram-sha-256
+# Allow replication connections from localhost, by a user with the
+# replication privilege.
+#local   replication     all                                     trust
+#host    replication     all             127.0.0.1/32            scram-sha-256
+#host    replication     all             ::1/128                 scram-sha-256
+
+host pdns pdns all scram-sha-256
+host all all all reject

+ 4 - 0
docker-compose.dev.yml

@@ -28,6 +28,10 @@ services:
     logging:
       driver: "json-file"
 
+  dbmaster-legacy:
+    logging:
+      driver: "json-file"
+
   api:
     environment:
     - DESECSTACK_API_DEBUG=True

+ 28 - 2
docker-compose.yml

@@ -103,6 +103,30 @@ services:
     build: dbmaster
     image: desec/dedyn-dbmaster:latest
     init: true
+    user: postgres:postgres
+    shm_size: 256M
+    volumes:
+    - dbmaster_postgres:/var/lib/postgresql/data
+    - ./dbmaster/pg_hba.conf:/usr/local/src/pg_hba.conf:ro
+    environment:
+    - POSTGRES_DB=pdns
+    - POSTGRES_HOST_AUTH_METHOD=reject
+    - POSTGRES_INITDB_ARGS=--auth-host=scram-sha-256
+    - POSTGRES_NON_ROOT_USER=pdns
+    - POSTGRES_NON_ROOT_USER_PASSWORD=${DESECSTACK_DBMASTER_PASSWORD_pdns}
+    networks:
+    - rearmaster
+    command: ["postgres", "-c", "hba_file=/usr/local/src/pg_hba.conf"]
+    logging:
+      driver: "syslog"
+      options:
+        tag: "desec/dbmaster"
+    restart: unless-stopped
+
+  dbmaster-legacy:
+    build: dbmaster-legacy
+    image: desec/dedyn-dbmaster-legacy:latest
+    init: true
     user: mysql:mysql
     volumes:
     - dbmaster_mysql:/var/lib/mysql
@@ -114,7 +138,7 @@ services:
     logging:
       driver: "syslog"
       options:
-        tag: "desec/dbmaster"
+        tag: "desec/dbmaster-legacy"
     restart: unless-stopped
 
   api:
@@ -208,6 +232,7 @@ services:
     - DESECSTACK_NSMASTER_TSIGKEY
     depends_on:
     - dbmaster
+    - dbmaster-legacy
     networks:
       rearapi_ns:
         ipv4_address: ${DESECSTACK_IPV4_REAR_PREFIX16}.1.12
@@ -218,7 +243,7 @@ services:
       driver: "syslog"
       options:
         tag: "desec/nsmaster"
-    restart: unless-stopped
+    restart: "no"
 
   rabbitmq:
     image: rabbitmq:3.8-alpine
@@ -418,6 +443,7 @@ volumes:
   dbapi_postgres:
   dblord_mysql:
   dbmaster_mysql:
+  dbmaster_postgres:
   openvpn-server_logs:
   prometheus:
   rabbitmq_data:

+ 1 - 1
nsmaster/Dockerfile

@@ -19,7 +19,7 @@ RUN echo 'deb [arch=amd64] http://repo.powerdns.com/ubuntu bionic-auth-46 main'
 RUN set -ex \
 	&& apt-key adv --keyserver keyserver.ubuntu.com --recv 0x1B0C6205FD380FBB \
 	&& apt-get update \
-	&& apt-get install -y pdns-server pdns-backend-mysql \
+	&& apt-get install -y pdns-server pdns-backend-mysql pdns-backend-pgsql postgresql-client-10 \
 	# credentials management via envsubst
 	&& apt-get -y install gettext-base \
 	# VPN route

+ 32 - 0
nsmaster/conf/pdns-migrate.conf.var

@@ -0,0 +1,32 @@
+api=yes
+api-key=${DESECSTACK_NSMASTER_APIKEY}
+allow-axfr-ips=10.8.0.0/24
+also-notify=239.1.2.3,${DESECSTACK_NSMASTER_ALSO_NOTIFY}
+only-notify=
+setgid=pdns
+setuid=pdns
+secondary=yes
+secondary-do-renotify=yes
+send-signed-notify=no
+max-tcp-connections=200
+version-string=powerdns
+webserver=yes
+webserver-address=${DESECSTACK_IPV4_REAR_PREFIX16}.1.12
+webserver-allow-from=${DESECSTACK_IPV4_REAR_PREFIX16}.1.10
+webserver-max-bodysize=16
+carbon-server=${DESECSTACK_NSMASTER_CARBONSERVER}
+carbon-ourname=${DESECSTACK_NSMASTER_CARBONOURNAME}
+
+launch=gmysql,gpgsql
+gmysql-host=dbmaster-legacy
+gmysql-port=
+gmysql-dbname=pdns
+gmysql-user=pdns
+gmysql-password=${DESECSTACK_DBMASTER_PASSWORD_pdns}
+gmysql-dnssec=yes
+gpgsql-host=dbmaster
+gpgsql-port=5432
+gpgsql-dbname=pdns
+gpgsql-user=pdns
+gpgsql-password=${DESECSTACK_DBMASTER_PASSWORD_pdns}
+gpgsql-dnssec=yes

+ 7 - 7
nsmaster/conf/pdns.conf.var

@@ -17,10 +17,10 @@ webserver-max-bodysize=16
 carbon-server=${DESECSTACK_NSMASTER_CARBONSERVER}
 carbon-ourname=${DESECSTACK_NSMASTER_CARBONOURNAME}
 
-launch=gmysql
-gmysql-host=dbmaster
-gmysql-port=
-gmysql-dbname=pdns
-gmysql-user=pdns
-gmysql-password=${DESECSTACK_DBMASTER_PASSWORD_pdns}
-gmysql-dnssec=yes
+launch=gpgsql
+gpgsql-host=dbmaster
+gpgsql-port=5432
+gpgsql-dbname=pdns
+gpgsql-user=pdns
+gpgsql-password=${DESECSTACK_DBMASTER_PASSWORD_pdns}
+gpgsql-dnssec=yes

+ 11 - 1
nsmaster/entrypoint.sh

@@ -8,10 +8,20 @@
 # TODO remove this workaround once the problem has been solved at its root
 iptables -t mangle -A OUTPUT -p udp -j TTL --ttl-set 64
 
-host=dbmaster; port=3306; n=120; i=0; while ! (echo > /dev/tcp/$host/$port) 2> /dev/null; do [[ $i -eq $n ]] && >&2 echo "$host:$port not up after $n seconds, exiting" && exit 1; echo "waiting for $host:$port to come up"; sleep 1; i=$((i+1)); done
+host=dbmaster-legacy; port=3306; n=120; i=0; while ! (echo > /dev/tcp/$host/$port) 2> /dev/null; do [[ $i -eq $n ]] && >&2 echo "$host:$port not up after $n seconds, exiting" && exit 1; echo "waiting for $host:$port to come up"; sleep 1; i=$((i+1)); done
+# wait for dbmaster database to come up
+until PGPASSWORD=$DESECSTACK_DBMASTER_PASSWORD_pdns psql -h dbmaster -U pdns -c '\q'; do
+  >&2 echo "Postgres is unavailable - sleeping"
+  sleep 1
+done
 
 # Manage credentials
 envsubst < /etc/powerdns/pdns.conf.var > /etc/powerdns/pdns.conf
+envsubst < /etc/powerdns/pdns-migrate.conf.var > /etc/powerdns/pdns-migrate.conf
+
+# Migrate
+pdnsutil --config-name migrate b2b-migrate gmysql gpgsql
+pdnsutil rectify-all-zones
 
 echo "Provisioning default TSIG key ..."
 pdnsutil import-tsig-key default hmac-sha256 "${DESECSTACK_NSMASTER_TSIGKEY}" > /dev/null