Commit f6b1f3cc authored by Vincent Breitmoser's avatar Vincent Breitmoser
Browse files

wkd: update a bit, and add to flake

parent 3293dd8f
Loading
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -8,9 +8,13 @@
      pkgs = nixpkgs.legacyPackages."${system}";
    in rec {
      packages.hagrid = pkgs.callPackage ./. { };
      packages.wkdDomainChecker = pkgs.callPackage ./wkd-domain-checker/. { };

      packages.default = packages.hagrid;
    }) // {
      overlays.hagrid = (final: prev: { hagrid = self.packages."${final.system}".hagrid; });
      overlays.wkdDomainChecker = (final: prev: { wkdDomainChecker = self.packages."${final.system}".wkdDomainChecker; });

      overlays.default = self.overlays.hagrid;
    };
}
+22 −0
Original line number Diff line number Diff line
{ lib, python3Packages }:

python3Packages.buildPythonApplication {
  pname = "wkd-domain-checker";
  version = "1.0";

  propagatedBuildInputs = with python3Packages; [
    flask
    publicsuffix2
    requests
  ];

  src = ./.;

  meta = with lib; {
    description = "WKD domain checker for hagrid wkd gateway";
    homepage = "https://gitlab.com/keys.openpgp.org/hagrid";
    license = with licenses; [ gpl3 ];
    maintainers = with maintainers; [ valodim ];
    platforms = platforms.all;
  };
}
+13 −12
Original line number Diff line number Diff line
certifi==2019.11.28
chardet==3.0.4
Click==7.0
Flask==1.1.1
gunicorn==20.0.4
idna==2.8
itsdangerous==1.1.0
Jinja2==2.11.0
MarkupSafe==1.1.1
# just for reference, this is canonically built using default.nix
blinker==1.9.0
certifi==2025.1.31
charset-normalizer==3.4.1
click==8.1.8
Flask==3.1.0
idna==3.10
itsdangerous==2.2.0
Jinja2==3.1.5
MarkupSafe==3.0.2
publicsuffix2==2.20191221
requests==2.22.0
urllib3==1.25.8
Werkzeug==0.16.1
requests==2.32.3
urllib3==2.3.0
Werkzeug==3.1.3
+12 −0
Original line number Diff line number Diff line
#!/usr/bin/env python

from setuptools import setup, find_packages

setup(
  name='wkd-domain-checker',
  version='1.0',
  # Modules to import from other scripts:
  packages=find_packages(),
  # Executables
  scripts=["wkd-domain-checker.py"],
)
+33 −19
Original line number Diff line number Diff line
#!/usr/bin/env python

# Simple flask server that checks whether a domain is allowed as a WKD target.
# Most importantly, this determines whether we attempt to request a certificate
# from letsencrypt for it.
@@ -10,25 +12,35 @@
# - it must be a CNAME that points to wkd.keys.openpgp.org. We do a simple DoH
#   request to cloudflare to make sure it looks correct from someone else's
#   perspective.
#
# Configuration via environment variables:
#
# - FLASK_GATEWAY_DOMAIN must be set to gateway domain (e.g. wkd.keys.openpgp.org)
# - FLASK_ALLOWLIST_FILE may point to a file that contains an explicit allowlist of domains, one per line

import requests
from publicsuffix2 import get_sld
from flask import Flask, request, abort, escape
app = Flask(__name__)
import publicsuffix2
import flask
import markupsafe
import sys

GATEWAY_DOMAIN = 'wkd.keys.openpgp.org'
app = flask.Flask('wkd-domain-checker')
app.config.from_prefixed_env()
if 'GATEWAY_DOMAIN' not in app.config:
    app.logger.error('missing config: FLASK_GATEWAY_DOMAIN')
    sys.exit(1)

# a manual whitelist of domains. we don't allow arbitrary subdomains for abuse
# reasons, but other entries are generally possible. just ask.
WHITELIST = [
    'openpgpkey.keys.openpgp.org',
    'openpgpkey.my.amazin.horse'
]
app.config.from_envvar('ALLOWLIST_FILE', silent=True)
if 'ALLOWLIST_FILE' in app.config:
    with open(app.config['ALLOWLIST_FILE'], 'r') as f:
        app.config['EXPLICIT_ALLOWED_DOMAINS'] = f.read().splitlines()
else:
    app.config['EXPLICIT_ALLOWED_DOMAINS'] = []

@app.route('/status/')
@app.route('/')
def check():
    domain = request.args.get('domain')
    domain = flask.request.args.get('domain')
    if not domain:
        return 'missing parameter: domain\n', 400

@@ -37,13 +49,15 @@ def check():
    return result

def check_domain(domain):
    if domain in WHITELIST:
        return 'ok: domain is whitelisted\n'
    # check allowlist of domains. we don't allow arbitrary subdomains for abuse
    # reasons, but other entries are generally possible. just ask.
    if domain in app.config['EXPLICIT_ALLOWED_DOMAINS']:
        return 'ok: domain is allowlisted\n'

    if not domain.startswith('openpgpkey.'):
        return 'domain must have "openpgpkey" prefix\n', 400

    if domain != ("openpgpkey." + get_sld(domain)):
    if domain != ("openpgpkey." + publicsuffix2.get_sld(domain)):
        return 'subdomains can only be used upon request. send an email to <tt>support at keys dot openpgp dot org</tt>\n', 400

    req = requests.get(
@@ -60,7 +74,7 @@ def check_domain(domain):

    if req.status_code != 200:
        app.logger.debug(f'dns error: {req.status_code} {req.text})')
        abort(400, f'CNAME lookup failed (http {req.status_code})')
        flask.abort(400, f'CNAME lookup failed (http {req.status_code})')
    response = req.json()
    app.logger.debug(f'response json: {response}')

@@ -76,10 +90,10 @@ def check_domain(domain):
    if answer['type'] != 5:
        return 'CNAME lookup failed: unexpected response (record type)\n', 400
    if answer['name'] != domain and answer['name'] != f'{domain}.':
        return f'CNAME lookup failed: unexpected response (domain response was for {escape(domain)})\n', 400
    if not answer['data'].startswith(GATEWAY_DOMAIN):
        return f'CNAME lookup failed: {escape(domain)} resolves to {escape(answer["data"])} (expected {GATEWAY_DOMAIN})\n', 400
    return f'CNAME lookup ok: {escape(domain)} resolves to {GATEWAY_DOMAIN}\n'
        return f'CNAME lookup failed: unexpected response (domain response was for {markupsafe.escape(domain)})\n', 400
    if not answer['data'].startswith(app.config['GATEWAY_DOMAIN']):
        return f'CNAME lookup failed: {markupsafe.escape(domain)} resolves to {markupsafe.escape(answer["data"])} (expected {GATEWAY_DOMAIN})\n', 400
    return f'CNAME lookup ok: {markupsafe.escape(domain)} resolves to {GATEWAY_DOMAIN}\n'

if __name__ == '__main__':
    app.run()