diff --git a/bin/quickstart.sh b/bin/quickstart.sh --- a/bin/quickstart.sh +++ b/bin/quickstart.sh @@ -57,7 +57,7 @@ bin/regen-certs -docker-compose up -d coturn kolab mariadb openvidu kurento-media-server proxy redis +docker-compose up -d coturn kolab mariadb openvidu kurento-media-server pdns-sql proxy redis pushd ${base_dir}/src/ diff --git a/docker-compose.yml b/docker-compose.yml --- a/docker-compose.yml +++ b/docker-compose.yml @@ -100,6 +100,23 @@ tty: true volumes: - /etc/letsencrypt/:/etc/letsencrypt/:ro + pdns-sql: + build: + context: ./docker/pdns-sql/ + container_name: kolab-pdns-sql + depends_on: + - mariadb + hostname: pdns-sql + image: apheleia/kolab-pdns-sql + network_mode: host + tmpfs: + - /run + - /tmp + - /var/run + - /var/tmp + tty: true + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro proxy: build: context: ./docker/proxy/ diff --git a/docker/pdns-sql/.gitignore b/docker/pdns-sql/.gitignore new file mode 100644 --- /dev/null +++ b/docker/pdns-sql/.gitignore @@ -0,0 +1,2 @@ +*.controlsocket +*.pid diff --git a/docker/pdns-sql/Dockerfile b/docker/pdns-sql/Dockerfile new file mode 100644 --- /dev/null +++ b/docker/pdns-sql/Dockerfile @@ -0,0 +1,28 @@ +FROM fedora:33 + +ENV container docker +ENV SYSTEMD_PAGER='' + +RUN dnf -y install \ + --setopt 'tsflags=nodocs' \ + bind-utils \ + cronie \ + iproute \ + iptables \ + net-tools \ + pdns \ + pdns-backend-mysql \ + pdns-tools \ + procps-ng \ + vim-enhanced \ + wget \ + which && \ + dnf clean all + +COPY pdns.conf /etc/pdns/pdns.conf + +RUN systemctl enable pdns + +WORKDIR /root/ + +CMD ["/lib/systemd/systemd", "--system"] diff --git a/docker/pdns-sql/pdns.conf b/docker/pdns-sql/pdns.conf new file mode 100644 --- /dev/null +++ b/docker/pdns-sql/pdns.conf @@ -0,0 +1,70 @@ +launch=gmysql +log-dns-details +query-logging=yes + +local-address=127.0.0.1:9953 +local-ipv6=[::1]:9953 + +edns-subnet-processing + +gmysql-host=127.0.0.1 +gmysql-dbname=kolabdev +gmysql-password=kolab +gmysql-user=kolabdev + +gmysql-activate-domain-key-query=UPDATE powerdns_cryptokeys SET active=1 WHERE domain_id=(SELECT id FROM domains WHERE name=?) AND powerdns_cryptokeys.id=? +gmysql-add-domain-key-query=INSERT INTO powerdns_cryptokeys (domain_id, flags, active, content) SELECT id, ?, ?, ? FROM powerdns_domains WHERE name=? +gmysql-any-id-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE disabled=0 AND name=? AND domain_id=? +gmysql-any-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE disabled=0 AND name=? +gmysql-basic-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE disabled=0 AND type=? AND name=? +gmysql-clear-domain-all-keys-query=delete FROM powerdns_cryptokeys WHERE domain_id=(SELECT id FROM powerdns_domains WHERE name=?) +gmysql-clear-domain-all-metadata-query=delete FROM powerdns_domain_settings WHERE domain_id=(SELECT id FROM powerdns_domains WHERE name=?) +gmysql-clear-domain-metadata-query=delete FROM powerdns_domain_settings WHERE domain_id=(SELECT id FROM powerdns_domains WHERE name=?) AND powerdns_domain_settings.kind=? +gmysql-deactivate-domain-key-query=UPDATE powerdns_cryptokeys SET active=0 WHERE domain_id=(SELECT id FROM powerdns_domains WHERE name=?) AND powerdns_cryptokeys.id=? +gmysql-delete-comment-rrset-query=DELETE FROM powerdns_comments WHERE domain_id=? AND name=? AND type=? +gmysql-delete-comments-query=DELETE FROM powerdns_comments WHERE domain_id=? +gmysql-delete-domain-query=delete FROM powerdns_domains WHERE name=? +gmysql-delete-empty-non-terminal-query=delete FROM powerdns_records WHERE domain_id=? AND name=? AND type is null +gmysql-delete-names-query=delete FROM powerdns_records WHERE domain_id=? AND name=? +gmysql-delete-rrset-query=delete FROM powerdns_records WHERE domain_id=? AND name=? AND type=? +gmysql-delete-tsig-key-query=delete FROM powerdns_tsigkeys WHERE name=? +gmysql-delete-zone-query=delete FROM powerdns_records WHERE domain_id=? +gmysql-get-all-domain-metadata-query=SELECT kind,content FROM powerdns_domains, powerdns_domain_settings WHERE powerdns_domain_settings.domain_id=powerdns_domains.id AND name=? +gmysql-get-all-domains-query=SELECT powerdns_domains.id, powerdns_domains.name, powerdns_records.content, powerdns_domains.type, powerdns_domains.master, powerdns_domains.notified_serial, powerdns_domains.last_check, powerdns_domains.account FROM powerdns_domains LEFT JOIN powerdns_records ON powerdns_records.domain_id=powerdns_domains.id AND powerdns_records.type='SOA' AND powerdns_records.name=domains.name WHERE powerdns_records.disabled=0 OR ? +gmysql-get-domain-metadata-query=SELECT content FROM powerdns_domains, powerdns_domain_settings WHERE powerdns_domain_settings.domain_id=powerdns_domains.id AND name=? AND powerdns_domain_settings.kind=? +gmysql-get-last-inserted-key-id-query=SELECT LAST_INSERT_ID() +gmysql-get-order-after-query=SELECT ordername FROM powerdns_records WHERE ordername > ? AND domain_id=? AND disabled=0 AND ordername IS NOT NULL ORDER BY 1 ASC LIMIT 1 +gmysql-get-order-before-query=SELECT ordername, name FROM powerdns_records WHERE ordername <= ? AND domain_id=? AND disabled=0 AND ordername IS NOT NULL ORDER BY 1 desc LIMIT 1 +gmysql-get-order-first-query=SELECT ordername FROM powerdns_records WHERE domain_id=? AND disabled=0 AND ordername IS NOT NULL ORDER BY 1 ASC LIMIT 1 +gmysql-get-order-last-query=SELECT ordername, name FROM powerdns_records WHERE ordername != '' AND domain_id=? AND disabled=0 AND ordername IS NOT NULL ORDER BY 1 desc LIMIT 1 +gmysql-get-tsig-key-query=SELECT algorithm, secret FROM powerdns_tsigkeys WHERE name=? +gmysql-get-tsig-keys-query=SELECT name,algorithm, secret FROM powerdns_tsigkeys +gmysql-id-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE disabled=0 AND type=? AND name=? AND domain_id=? +gmysql-info-all-master-query=SELECT d.id, d.name, d.notified_serial, r.content FROM powerdns_records r join powerdns_domains d on r.name=d.name WHERE r.type='SOA' AND r.disabled=0 AND d.type='MASTER' +gmysql-info-all-slaves-query=SELECT id,name,master,last_check FROM powerdns_domains WHERE type='SLAVE' +gmysql-info-zone-query=SELECT id,name,master,last_check,notified_serial,type,account FROM powerdns_domains WHERE name=? +gmysql-insert-comment-query=INSERT INTO powerdns_comments (domain_id, name, type, modified_at, account, comment) VALUES (?, ?, ?, ?, ?, ?) +gmysql-insert-empty-non-terminal-order-query=INSERT INTO powerdns_records (type,domain_id,disabled,name,ordername,auth,content,ttl,prio) values (null,?,0,?,?,?,NULL,NULL,NULL) +gmysql-insert-record-query=INSERT INTO powerdns_records (content,ttl,prio,type,domain_id,disabled,name,ordername,auth) values (?,?,?,?,?,?,?,?,?) +gmysql-insert-zone-query=INSERT INTO powerdns_domains (type,name,master,account,last_check,notified_serial) values(?,?,?,?,NULL,NULL) +gmysql-list-comments-query=SELECT domain_id,name,type,modified_at,account,comment FROM powerdns_comments WHERE domain_id=? +gmysql-list-domain-keys-query=SELECT powerdns_cryptokeys.id, flags, active, content FROM powerdns_domains, powerdns_cryptokeys WHERE powerdns_cryptokeys.domain_id=powerdns_domains.id AND name=? +gmysql-list-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE (disabled=0 OR ?) AND domain_id=? ORDER BY name, type +gmysql-list-subzone-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE disabled=0 AND (name=? OR name like ?) AND domain_id=? +gmysql-nullify-ordername-and-update-auth-query=UPDATE powerdns_records SET ordername=NULL,auth=? WHERE domain_id=? AND name=? AND disabled=0 +gmysql-nullify-ordername-and-update-auth-type-query=UPDATE powerdns_records SET ordername=NULL,auth=? WHERE domain_id=? AND name=? AND type=? AND disabled=0 +gmysql-remove-domain-key-query=delete FROM powerdns_cryptokeys WHERE domain_id=(SELECT id FROM powerdns_domains WHERE name=?) AND powerdns_cryptokeys.id=? +gmysql-remove-empty-non-terminals-from-zone-query=delete FROM powerdns_records WHERE domain_id=? AND type is null +gmysql-search-comments-query=SELECT domain_id,name,type,modified_at,account,comment FROM powerdns_comments WHERE name LIKE ? OR comment LIKE ? LIMIT ? +gmysql-search-records-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM powerdns_records WHERE name LIKE ? OR content LIKE ? LIMIT ? +gmysql-set-domain-metadata-query=INSERT INTO powerdns_domain_settings (domain_id, kind, content) SELECT id, ?, ? FROM powerdns_domains WHERE name=? +gmysql-set-tsig-key-query=REPLACE INTO powerdns_tsigkeys (name,algorithm,secret) values(?,?,?) +gmysql-supermaster-query=SELECT account FROM powerdns_masters WHERE ip=? AND nameserver=? +gmysql-update-account-query=UPDATE powerdns_domains SET account=? WHERE name=? +gmysql-update-kind-query=UPDATE powerdns_domains SET type=? WHERE name=? +gmysql-update-lastcheck-query=UPDATE powerdns_domains SET last_check=? WHERE id=? +gmysql-update-master-query=UPDATE powerdns_domains SET master=? WHERE name=? +gmysql-update-ordername-and-auth-query=UPDATE powerdns_records SET ordername=?,auth=? WHERE domain_id=? AND name=? AND disabled=0 +gmysql-update-ordername-and-auth-type-query=UPDATE powerdns_records SET ordername=?,auth=? WHERE domain_id=? AND name=? AND type=? AND disabled=0 +gmysql-update-serial-query=UPDATE powerdns_domains SET notified_serial=? WHERE id=? + diff --git a/src/app/Console/Commands/PowerDNS/Domain/CreateCommand.php b/src/app/Console/Commands/PowerDNS/Domain/CreateCommand.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/PowerDNS/Domain/CreateCommand.php @@ -0,0 +1,44 @@ +argument('domain'); + + \App\PowerDNS\Domain::create(['name' => $name]); + } +} diff --git a/src/app/Console/Commands/PowerDNS/Domain/DeleteCommand.php b/src/app/Console/Commands/PowerDNS/Domain/DeleteCommand.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/PowerDNS/Domain/DeleteCommand.php @@ -0,0 +1,50 @@ +argument('domain'); + + $domain = \App\PowerDNS\Domain::where('name', $name)->first(); + + if (!$domain) { + return 1; + } + + $domain->delete(); + } +} diff --git a/src/app/Console/Commands/PowerDNS/Domain/ReadCommand.php b/src/app/Console/Commands/PowerDNS/Domain/ReadCommand.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/PowerDNS/Domain/ReadCommand.php @@ -0,0 +1,52 @@ +argument('domain'); + + $domain = \App\PowerDNS\Domain::where('name', $name)->first(); + + if (!$domain) { + return 1; + } + + foreach ($domain->records as $record) { + $this->info($record->toString()); + } + } +} diff --git a/src/app/Observers/PowerDNS/DomainObserver.php b/src/app/Observers/PowerDNS/DomainObserver.php new file mode 100644 --- /dev/null +++ b/src/app/Observers/PowerDNS/DomainObserver.php @@ -0,0 +1,69 @@ + $domain->id, + 'name' => $domain->{'name'}, + 'type' => "SOA", + 'content' => sprintf( + "ns.%s. hostmaster.%s. %s 1200 600 1814400 60", + $domain->{'name'}, + $domain->{'name'}, + \Carbon\Carbon::now()->format('Ymd') . '01' + ) + ] + ); + + \App\PowerDNS\Record::create( + [ + 'domain_id' => $domain->id, + 'name' => $domain->{'name'}, + 'type' => "NS", + 'content' => "ns1." . $domain->{'name'} . "." + ] + ); + + \App\PowerDNS\Record::create( + [ + 'domain_id' => $domain->id, + 'name' => "ns1." . $domain->{'name'}, + 'type' => "A", + 'content' => "127.0.0.1" + ] + ); + + \App\PowerDNS\Record::create( + [ + 'domain_id' => $domain->id, + 'name' => $domain->{'name'}, + 'type' => "NS", + 'content' => "ns2." . $domain->{'name'} . "." + ] + ); + + \App\PowerDNS\Record::create( + [ + 'domain_id' => $domain->id, + 'name' => "ns2." . $domain->{'name'}, + 'type' => "A", + 'content' => "127.0.0.1" + ] + ); + + \App\PowerDNS\DomainSetting::create( + [ + 'domain_id' => $domain->id, + 'kind' => 'ENABLE-LUA-RECORDS', + 'content' => "0" + ] + ); + } +} diff --git a/src/app/Observers/PowerDNS/RecordObserver.php b/src/app/Observers/PowerDNS/RecordObserver.php new file mode 100644 --- /dev/null +++ b/src/app/Observers/PowerDNS/RecordObserver.php @@ -0,0 +1,35 @@ +{'type'} == "SOA") { + return; + } + + $record->domain->bumpSerial(); + } + + public function deleted($record) + { + if ($record->{'type'} == "SOA") { + return; + } + + $record->domain->bumpSerial(); + } + + public function updated($record) + { + if ($record->{'type'} == "SOA") { + return; + } + + $record->domain->bumpSerial(); + } +} diff --git a/src/app/PowerDNS/Comment.php b/src/app/PowerDNS/Comment.php new file mode 100644 --- /dev/null +++ b/src/app/PowerDNS/Comment.php @@ -0,0 +1,10 @@ + $this->id, + 'type' => 'SOA' + ] + )->first(); + + list($ns, $hm, $serial, $a, $b, $c, $d) = explode(" ", $soa->content); + + $today = \Carbon\Carbon::now()->format('Ymd'); + $date = substr($serial, 0, 8); + + if ($date != $today) { + $serial = $today . '01'; + } else { + $change = (int)(substr($serial, 8, 2)); + + $serial = sprintf("%s%02s", $date, ($change + 1)); + } + + $soa->content = "{$ns} {$hm} {$serial} {$a} {$b} {$c} {$d}"; + $soa->save(); + } + + public function getSerial() + { + $soa = Record::where( + [ + 'domain_id' => $this->id, + 'type' => 'SOA' + ] + )->first(); + + list($ns, $hm, $serial, $a, $b, $c, $d) = explode(" ", $soa->content); + + return $serial; + } + + public function records() + { + return $this->hasMany('App\PowerDNS\Record', 'domain_id'); + } + + //public function setSerial() { } +} diff --git a/src/app/PowerDNS/DomainSetting.php b/src/app/PowerDNS/DomainSetting.php new file mode 100644 --- /dev/null +++ b/src/app/PowerDNS/DomainSetting.php @@ -0,0 +1,16 @@ +belongsTo('App\PowerDNS\Domain', 'domain_id', 'id'); + } + + public function toString() + { + return "{$this->name}. {$this->type} {$this->content}"; + } +} diff --git a/src/app/PowerDNS/SuperMaster.php b/src/app/PowerDNS/SuperMaster.php new file mode 100644 --- /dev/null +++ b/src/app/PowerDNS/SuperMaster.php @@ -0,0 +1,10 @@ +bigIncrements('id'); + $table->string('name', 255)->unique()->index(); + $table->string('master', 128)->nullable(); + $table->datetime('last_check')->nullable(); + $table->string('type', 6)->default('NATIVE'); + $table->integer('notified_serial')->unsigned()->nullable(); + $table->string('account', 40)->nullable(); + $table->timestamps(); + } + ); + + Schema::create( + 'powerdns_records', + function (Blueprint $table) { + $table->bigIncrements('id'); + $table->bigInteger('domain_id')->unsigned(); + $table->string('name', 255)->nullable(); + $table->string('type', 10)->nullable(); + $table->longtext('content')->nullable(); + $table->integer('ttl')->unsigned()->nullable(); + $table->integer('prio')->unsigned()->nullable(); + $table->boolean('disabled')->default(false); + $table->binary('ordername', 255)->nullable(); + $table->boolean('auth')->default(true); + $table->timestamps(); + + $table->foreign('domain_id')->references('id')->on('powerdns_domains') + ->onDelete('cascade'); + + $table->index('domain_id'); + $table->index(['name', 'type']); + $table->index('ordername'); + } + ); + + Schema::create( + 'powerdns_masters', + function (Blueprint $table) { + $table->string('ip', 64); + $table->string('nameserver', 255); + $table->string('account', 40); + + $table->primary(['ip', 'nameserver']); + + $table->timestamps(); + } + ); + + Schema::create( + 'powerdns_comments', + function (Blueprint $table) { + $table->bigIncrements('id'); + $table->bigInteger('domain_id')->unsigned(); + $table->string('name', 255); + $table->string('type', 10); + $table->string('account', 40)->nullable(); + $table->text('comment'); + $table->timestamps(); + + $table->index(['name', 'type']); + $table->index(['domain_id', 'updated_at']); + + $table->foreign('domain_id')->references('id')->on('powerdns_domains') + ->onDelete('cascade'); + } + ); + + Schema::create( + 'powerdns_domain_settings', + function (Blueprint $table) { + $table->bigIncrements('id'); + $table->bigInteger('domain_id')->unsigned(); + $table->string('kind', 32); + $table->text('content'); + $table->timestamps(); + + $table->foreign('domain_id')->references('id')->on('powerdns_domains') + ->onDelete('cascade'); + } + ); + + Schema::create( + 'powerdns_cryptokeys', + function (Blueprint $table) { + $table->bigIncrements('id'); + $table->bigInteger('domain_id')->unsigned(); + $table->integer('flags'); + $table->boolean('active'); + $table->text('content'); + $table->timestamps(); + + $table->index('domain_id'); + + $table->foreign('domain_id')->references('id')->on('powerdns_domains') + ->onDelete('cascade'); + } + ); + + Schema::create( + 'powerdns_tsigkeys', + function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('name', 255); + $table->string('algorithm', 50); + $table->string('secret', 255); + $table->timestamps(); + + $table->index(['name', 'algorithm']); + } + ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('powerdns_tsigkeys'); + Schema::dropIfExists('powerdns_cryptokeys'); + Schema::dropIfExists('powerdns_domain_settings'); + Schema::dropIfExists('powerdns_comments'); + Schema::dropIfExists('powerdns_masters'); + Schema::dropIfExists('powerdns_records'); + Schema::dropIfExists('powerdns_domains'); + } +} diff --git a/src/database/seeds/DatabaseSeeder.php b/src/database/seeds/DatabaseSeeder.php --- a/src/database/seeds/DatabaseSeeder.php +++ b/src/database/seeds/DatabaseSeeder.php @@ -21,6 +21,7 @@ 'SkuSeeder', 'PackageSeeder', 'PlanSeeder', + 'PowerDNSSeeder', 'UserSeeder', 'OpenViduRoomSeeder', ]; diff --git a/src/database/seeds/local/PowerDNSSeeder.php b/src/database/seeds/local/PowerDNSSeeder.php new file mode 100644 --- /dev/null +++ b/src/database/seeds/local/PowerDNSSeeder.php @@ -0,0 +1,43 @@ + '_woat.' . \config('app.domain') + ] + ); + + /* + $domainLuaSetting = \App\PowerDNS\DomainSetting::where( + ['domain_id' => $domain->id, 'kind' => 'ENABLE-LUA-RECORDS'] + )->first(); + + $domainLuaSetting->{'content'} = "1"; + $domainLuaSetting->save(); + */ + + /* + \App\PowerDNS\Record::create( + [ + 'domain_id' => $domain->id, + 'name' => $domain->{'name'}, + 'type' => 'A', + 'content' => '10.4.2.23' + ] + ); + */ + } +} diff --git a/src/package-lock.json b/src/package-lock.json --- a/src/package-lock.json +++ b/src/package-lock.json @@ -5279,6 +5279,26 @@ "randomfill": "^1.0.3" } }, + "css": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", + "integrity": "sha1-xkZ1XHOXHyu6amAeLPL9cbEpiSk=", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", + "urix": "^0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "dev": true + } + } + }, "css-color-names": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-1.0.1.tgz", diff --git a/src/tests/Feature/PowerDNS/DomainTest.php b/src/tests/Feature/PowerDNS/DomainTest.php new file mode 100644 --- /dev/null +++ b/src/tests/Feature/PowerDNS/DomainTest.php @@ -0,0 +1,61 @@ +domain = Domain::firstOrCreate( + [ + 'name' => 'test-domain.com' + ] + ); + } + + /** + * {@inheritDoc} + */ + public function tearDown(): void + { + $this->domain->delete(); + + parent::tearDown(); + } + + public function testCreateRecord(): void + { + $before = $this->domain->getSerial(); + + Record::create( + [ + 'domain_id' => $this->domain->id, + 'name' => $this->domain->{'name'}, + 'type' => "MX", + 'content' => '10 mx01.' . $this->domain->{'name'} . '.' + ] + ); + + Record::create( + [ + 'domain_id' => $this->domain->id, + 'name' => 'mx01.' . $this->domain->{'name'}, + 'type' => "A", + 'content' => '127.0.0.1' + ] + ); + + $after = $this->domain->getSerial(); + + $this->assertTrue($before < $after); + } +}