Initial commit

This commit is contained in:
Finn 2024-01-17 09:45:49 -08:00
commit d6c3872aaa
23 changed files with 538 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/secrets

2
ansible.cfg Normal file
View file

@ -0,0 +1,2 @@
[defaults]
inventory=inventory.yml

16
inventory.yml Normal file
View file

@ -0,0 +1,16 @@
nameservers:
hosts:
dns.janky.solutions:
ansible_host: 10.5.1.156
powerdns_admin: yes
wireguard_ip: 10.6.0.1
wireguard_pubkey: hYUM1LRSemvjcPfHHcH9sZOsE45xWRSkasXs8uEDJDo=
wireguard_endpoint: wg.home.finn.io
ns1.janky.zone:
ansible_host: 137.184.226.48
wireguard_ip: 10.6.0.101
wireguard_pubkey: TwJXoSNhKhCCerjq1P8o3SBGQEe5vfjnB2Y9uX8mATU=
ns2.janky.zone:
ansible_host: 66.42.71.31
wireguard_ip: 10.6.0.102
wireguard_pubkey: gTa4wsiQCGu+rbH05U8bjDJPVzINKJ/BIY0FejSWrWs=

13
powerdns.md Normal file
View file

@ -0,0 +1,13 @@
# PowerDNS Infrastructure
Playbook `powerdns.yml` will do the core setup. The GUI requires some manual configuration unfortunately:
1. On first login, you will be prompted for a PowerDNS URL and password. URL is http://10.88.0.1:8081 (10.88.0.1 is the podman host IP on the default network), password is generated and written to `secrets/dns.janky.solutions/pdns-api-password.txt`
2. Navigate to Settings -> Basic and update the following settings, clicking each of their respective save buttons after updating the field (there is no global save button)
* `allow_user_create_domain` - turn on
* `allow_user_remove_domain` - turn on
* `allow_user_view_history` - turn on
* `default_domain_table_size` - `100`
* `default_record_table_size` - `1000`
* `session_timeout` - `99999` (a session timeout will trigger an SSO logout!)
* `site_name` - `Janky Solutions DNS`
* `ttl_options` - `1 minute,5 minutes,30 minutes,60 minutes,24 hours,48 hours`

6
powerdns.yml Normal file
View file

@ -0,0 +1,6 @@
- hosts: nameservers
vars:
ansible_user: root
roles:
- base
- pdns

View file

@ -0,0 +1,8 @@
- name: install common packages
apt:
name: [mosh, htop, tmux, unattended-upgrades]
- name: remove stupid bullshit that the cloud provider may have installed
apt:
name: [ufw]
state: absent

View file

@ -0,0 +1,6 @@
- name: Install monitoring tools
apt:
name: [prometheus-node-exporter, wireguard-tools]
- name: promtail
include_tasks: promtail.yml

View file

@ -0,0 +1,39 @@
- name: make /etc/apt/keyrings
file:
path: /etc/apt/keyrings
state: directory
- name: install grafana apt key
copy:
src: grafana-apt-key.gpg
dest: /etc/apt/keyrings/grafana.gpg
- name: add grafana apt repo
apt_repository:
repo: "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main"
filename: "grafana"
- name: install promtail
apt:
name: promtail
- name: make /etc/systemd/system/promtail.service.d
file:
path: /etc/systemd/system/promtail.service.d
state: directory
- name: install promtail.service override
template:
src: promtail-override.service
dest: /etc/systemd/system/promtail.service.d/override.conf
notify:
- systemctl daemon-reload
- restart promtail
- name: install promtail config
template:
src: promtail.yml
dest: /etc/promtail/config.yml
notify:
- restart promtail

View file

@ -0,0 +1,2 @@
[Service]
User=root

View file

@ -0,0 +1,33 @@
server:
log_level: warn
http_listen_port: 0
grpc_listen_port: 0
clients:
- url: https://loki.callpipe.com/loki/api/v1/push
external_labels:
hostname: "{{ inventory_hostname }}"
tls_config:
ca_file: /etc/step/certs/root_ca.crt
cert_file: /etc/step/certs/callpipe.crt
key_file: /etc/step/certs/callpipe.key
scrape_configs:
- job_name: journald
journal:
labels:
job: systemd-label
relabel_configs:
- source_labels: ['__journal__systemd_unit']
target_label: 'unit'
{% if 'jobs' in logs %}
{% for job_name, path in logs.jobs.items() %}
- job_name: {{ job_name }}
static_configs:
- targets:
- localhost
labels:
job: {{ job_name }}
__path__: {{ path }}
{% endfor %}
{% endif %}

View file

@ -0,0 +1,137 @@
- name: systemctl daemon-reload
command: systemctl daemon-reload
- name: restart systemd-resolved
service:
name: systemd-resolved
state: restarted
- name: restart marmot
service:
name: marmot
state: restarted
- name: restart postgresql
service:
name: postgresql
state: restarted
- name: create db schema
command: psql pdns
args:
stdin: |
CREATE TABLE domains (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
master VARCHAR(128) DEFAULT NULL,
last_check INT DEFAULT NULL,
type TEXT NOT NULL,
notified_serial BIGINT DEFAULT NULL,
account VARCHAR(40) DEFAULT NULL,
options TEXT DEFAULT NULL,
catalog TEXT DEFAULT NULL,
CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);
CREATE UNIQUE INDEX name_index ON domains(name);
CREATE INDEX catalog_idx ON domains(catalog);
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);
become: true
become_user: postgres
- name: restart pdns
service:
name: pdns
state: restarted
- name: restart powerdns-admin
service:
name: powerdns-admin
state: restarted
- name: restart wg-quick@wg0
service:
name: wg-quick@wg0
state: restarted

74
roles/pdns/tasks/main.yml Normal file
View file

@ -0,0 +1,74 @@
- name: install stuff from apt
apt:
name: [pdns-server, pdns-backend-pgsql, wireguard-tools, python3-psycopg2, postgresql]
- name: configure wireguard tunnel
template:
src: wireguard.conf
dest: /etc/wireguard/wg0.conf
notify:
- restart wg-quick@wg0
- name: enable the wireguard tunnel
service:
name: wg-quick@wg0
enabled: true
- name: check if resolved is installed
stat:
path: /etc/systemd/resolved.conf
register: resolvedconf
- name: create resolved.conf.d
file:
path: /etc/systemd/resolved.conf.d
state: directory
when: resolvedconf.stat.exists
- name: disable systemd-resolved stub listener (its probably using port 53 and we need it)
template:
src: systemd-resolved.conf
dest: /etc/systemd/resolved.conf.d/10-disable-stub-listener.conf
notify:
- restart systemd-resolved
when: resolvedconf.stat.exists
- name: configure postgres for streaming replication
template:
src: postgres.conf
dest: /etc/postgresql/15/main/conf.d/replication.conf
notify:
- restart postgresql
- name: configure postgres remote access
community.postgresql.postgresql_pg_hba:
address: 10.6.0.0/24
contype: host
databases: pdns
dest: /etc/postgresql/15/main/pg_hba.conf
notify:
- restart postgresql
when: powerdns_admin|default(false)
- meta: flush_handlers
- include_tasks:
file: postgresql-write.yml
apply:
become: true
become_user: postgres
when: powerdns_admin|default(false)
- include_tasks:
file: postgresql-read.yml
apply:
become: true
become_user: postgres
when: not powerdns_admin|default(false)
- include_tasks: powerdns.yml
- meta: flush_handlers
- include_tasks: powerdns-admin.yml
when: powerdns_admin|default(false)

View file

@ -0,0 +1,36 @@
- name: create db in postgres
community.postgresql.postgresql_db:
name: pdns
notify:
- create db schema
- meta: flush_handlers # schema must be created before permission grants happen
- name: create postgres pdns user
community.postgresql.postgresql_user:
name: pdns
db: pdns
password: "{{ lookup('ansible.builtin.password', 'secrets/' + inventory_hostname + '/pg-pdns-password.txt', length=15) }}"
- name: grant postgres pdns user permissions
community.postgresql.postgresql_privs:
database: pdns
roles: pdns
type: "{{ item }}"
privs: all
objs: ALL_IN_SCHEMA
with_items: ["table", "sequence"]
- name: create subscription
community.postgresql.postgresql_subscription:
db: pdns
name: pdns_{{ ansible_hostname }}
publications: pdns
connparams:
host: 10.6.0.1
port: 5432
user: "replication"
password: "{{ lookup('ansible.builtin.password', 'secrets/pg-replication-password.txt', length=15) }}"
dbname: pdns
subsparams:
copy_data: true

View file

@ -0,0 +1,42 @@
- name: create db in postgres
community.postgresql.postgresql_db:
name: pdns
notify:
- create db schema
- meta: flush_handlers # schema must be created before permission grants happen
- name: create postgres pdns user
community.postgresql.postgresql_user:
name: pdns
db: pdns
password: "{{ lookup('ansible.builtin.password', 'secrets/' + inventory_hostname + '/pg-pdns-password.txt', length=15) }}"
- name: grant postgres pdns user permissions
community.postgresql.postgresql_privs:
database: pdns
roles: pdns
type: "{{ item }}"
privs: all
objs: ALL_IN_SCHEMA
with_items: ["table", "sequence"]
- name: create postgres replication user
community.postgresql.postgresql_user:
name: replication
password: "{{ lookup('ansible.builtin.password', 'secrets/pg-replication-password.txt', length=15) }}"
role_attr_flags: replication
- name: grant postgres replication user permissions
community.postgresql.postgresql_privs:
database: pdns
roles: replication
type: "{{ item }}"
privs: all
objs: ALL_IN_SCHEMA
with_items: ["table", "sequence"]
- name: create postgresql publication
community.postgresql.postgresql_publication:
db: pdns
name: pdns

View file

@ -0,0 +1,23 @@
- name: install podman
apt:
name: [podman]
- name: install powerdns-admin service
template:
src: powerdns-admin.service
dest: /etc/systemd/system/powerdns-admin.service
notify:
- systemctl daemon-reload
- restart powerdns-admin
- name: configure powerdns-admin
template:
src: powerdns-admin.env
dest: /etc/powerdns-admin.env
notify:
- restart powerdns-admin
- name: enable powerdns-admin
service:
name: powerdns-admin
enabled: true

View file

@ -0,0 +1,13 @@
- name: configure powerdns
template:
src: powerdns.conf
dest: /etc/powerdns/pdns.d/config.conf
notify:
- restart pdns
- name: disable powerdns bind backend
file:
path: /etc/powerdns/pdns.d/bind.conf
state: absent
notify:
- restart pdns

View file

@ -0,0 +1,23 @@
# DO NOT DISABLE!
# If you change this first entry you will need to make sure that the
# database superuser can access the database using some other method.
# Noninteractive access to all databases is required during automatic
# maintenance (custom daily cronjobs, replication, and similar tasks).
#
# Database administrative login by Unix domain socket
local all postgres peer
# TYPE DATABASE USER ADDRESS METHOD
# "local" is for Unix domain socket connections only
local all all peer
# 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 peer
host replication all 127.0.0.1/32 scram-sha-256
host replication all ::1/128 scram-sha-256
host all all 10.6.0.0/24 md5

View file

@ -0,0 +1,7 @@
listen_addresses = 'localhost,{{ wireguard_ip }}'
{% if powerdns_admin|default(false) %}
# write replica specific settings
wal_level = logical
max_wal_senders = 5
{% endif %}

View file

@ -0,0 +1,13 @@
SECRET_KEY={{ lookup('ansible.builtin.ini', 'pdns_admin_secret section=pdns file=secrets/' + inventory_hostname + '.ini') }}
OIDC_OAUTH_ENABLED=true
OIDC_OAUTH_KEY=powerdnsadmin
OIDC_OAUTH_SECRET={{ lookup('ansible.builtin.ini', 'oidc_secret section=pdns file=secrets/' + inventory_hostname + '.ini') }}
OIDC_OAUTH_API_URL=https://auth.janky.solutions/auth/realms/janky.solutions/protocol/openid-connect/
OIDC_OAUTH_METADATA_URL=https://auth.janky.solutions/auth/realms/janky.solutions/.well-known/openid-configuration
OIDC_OAUTH_LOGOUT_URL=https://auth.janky.solutions/auth/realms/janky.solutions/protocol/openid-connect/logout
OIDC_OAUTH_USERNAME=preferred_username
OIDC_OAUTH_FIRSTNAME=given_name
OIDC_OAUTH_LAST_NAME=family_name
OIDC_OAUTH_EMAIL=email
SIGNUP_ENABLED=false
LOCAL_DB_ENABLED=false

View file

@ -0,0 +1,12 @@
[Unit]
Description=PowerDNS Admin
Wants=network.target
[Service]
Type=simple
ExecStartPre=/usr/bin/podman pull docker.io/powerdnsadmin/pda-legacy:latest
ExecStart=/usr/bin/podman run --rm -v pda-data:/data -p 9191:80 --env-file /etc/powerdns-admin.env --name powerdns-admin docker.io/powerdnsadmin/pda-legacy:latest
Restart=always
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,16 @@
launch=gpgsql
gpgsql-host=localhost
gpgsql-port=5432
gpgsql-dbname=pdns
gpgsql-user=pdns
gpgsql-password={{ lookup('ansible.builtin.password', 'secrets/' + inventory_hostname + '/pg-pdns-password.txt', length=15) }}
gpgsql-dnssec=yes
default-soa-content=ns1.janky.zone dns-admin.@ 0 10800 3600 604800 3600
{% if powerdns_admin | default(false) %}
api=yes
api-key={{ lookup('ansible.builtin.password', 'secrets/' + inventory_hostname + '/pdns-api-password.txt', length=15) }}
webserver-address=10.88.0.1
webserver-allow-from=10.88.0.0/24
{% endif %}
q

View file

@ -0,0 +1,2 @@
[Resolve]
DNSStubListener=no

View file

@ -0,0 +1,14 @@
[Interface]
PrivateKey = {{ lookup('ansible.builtin.ini', 'private_key section=wireguard file=secrets/' + inventory_hostname + '.ini') }}
ListenPort = 51822
Address = {{ wireguard_ip }}
{% for host in hostvars %}
{% if host != inventory_hostname %}
# {{ host }}
[Peer]
Endpoint = {{ hostvars[host].wireguard_endpoint|default(host) }}:51822
PublicKey = {{ hostvars[host].wireguard_pubkey }}
AllowedIPs = {{ hostvars[host].wireguard_ip }}
{% endif %}{% endfor %}