Start milestone 3

- Add functionality to query online sources (Keyservers, etc) for
  certificates.
- Use introducers to determine which UserIDs should be fetched from
  online soources. Check certifications before importing certifiactes
  into the local cert store.
- start docker image for local end-to-end testing.
This commit is contained in:
Malte Meiboom
2026-01-23 12:57:26 +01:00
parent 4270bf4ceb
commit 30b1d29274
23 changed files with 685 additions and 10 deletions
+96
View File
@@ -0,0 +1,96 @@
##
# build e2e tests
##
##
# build hockeypuck (keyserver), as it's not a debian package
##
FROM golang:1.24 AS gobuilder
## install build dependencies
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -yqq && \
apt-get install -yqq \
git \
make
WORKDIR /hockeypuck
RUN git clone https://github.com/hockeypuck/hockeypuck /hockeypuck
RUN make build
#
# the e2e tests need a rust toolchain
#
FROM docker.io/library/rust:latest
ENV DEBIAN_FRONTEND=noninteractive
## ensure language settings
RUN apt-get update -yqq && apt-get install -y locales && rm -rf /var/lib/apt/lists/* \
&& localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8
ENV LANG=en_US.utf8
## install build dependencies
RUN apt-get update -yqq && \
apt-get install -yqq \
clang \
git \
libclang-dev \
libsqlite3-dev \
libssl-dev \
llvm \
nettle-dev \
pkg-config
##
RUN apt-get update -yqq && \
apt-get install -yqq adduser \
passwd \
bash \
openssh-client \
sudo \
postfix \
rsyslog \
postgresql \
openssl \
sq
RUN adduser --shell /bin/bash --disabled-password --gecos "" user && \
mkdir -p /home/user/ && \
usermod -a -G sudo user && \
chown -R user:user /home/user
RUN adduser --shell /bin/bash --disabled-password --gecos "" alice && \
mkdir -p /home/alice/ && \
chown -R alice:alice /home/alice
RUN adduser --shell /bin/bash --disabled-password --gecos "" bob && \
mkdir -p /home/bob/ && \
chown -R bob:bob /home/bob
RUN adduser --shell /bin/bash --disabled-password --gecos "" carol && \
mkdir -p /home/carol/ && \
chown -R carol:carol /home/carol
RUN adduser --shell /bin/bash --disabled-password --gecos "" dave && \
mkdir -p /home/dave/ && \
chown -R dave:dave /home/dave
RUN adduser --shell /bin/bash --disabled-password --gecos "" hockeypuck
COPY mta_provision/ /
# copy hockeypuck
COPY --from=gobuilder /hockeypuck/bin/hockeypuck /usr/bin/hockeypuck
COPY --from=gobuilder /hockeypuck/contrib/templates/* /var/www/templates/
RUN /usr/bin/gen_hockeypuck.sh example.com 127.0.0.2
# tls certificate for example.com
RUN /usr/bin/gen_tls_cert.sh example.com && \
chown hockeypuck /etc/local_certs/example.com.key
RUN update-ca-certificates
+31
View File
@@ -0,0 +1,31 @@
# Content
This directory contains docker images and related provisioning files for setting
up test environments.
## MTA
- docker container file: MTAContainerFile
- provisioning: inside mta_provision/
### Build
```
$ docker build -f MTAContainerFile -t localhost/mta .
```
### Use
Check the image interactively
```
$ docker run -ti --rm localhost/mta bash -i
```
Use locally, no git clone and cargo build. This use case assumes that a binary
has been locally build and can be mapped into the docker image.
The mapped binary will be started, alongside `postfix` and `hockeypuck`.
```
$ docker run -ti --rm \
--mount type=bind,src=$(pwd)/target/release,dst=/opt/husk/mapped/
localhost/mta bash -c "/usr/bin/prepare.sh && bash -i"
```
+53
View File
@@ -0,0 +1,53 @@
#
# minimal postfix configuration with enabled milter
#
# Debian specific: Specifying a file name will cause the first
# line of that file to be used as the name. The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname
smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h
#readme_directory = /usr/share/doc/postfix
# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 3.6 on
# fresh installs.
compatibility_level = 3.6
# Per Debian policy, default to /etc/postfix for Cyrus SASL config
cyrus_sasl_config_path = /etc/postfix/sasl
# TLS parameters
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_tls_security_level=may
smtp_tls_CApath=/etc/ssl/certs
smtp_tls_security_level=may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = example.com
myorigin = example.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydestination = $myhostname, localhost.localdomain, localhost
#relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 192.168.1.0/24
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
smtpd_milters = inet:localhost:3000
non_smtpd_milters = $smtpd_milters
@@ -0,0 +1 @@
user ALL=(ALL) NOPASSWD:ALL
@@ -0,0 +1,7 @@
connection = "localhost:3000"
sequoia_home = "/tmp/sq_home"
logfile_config = "./log4rs.yml"
keyservers = [ "hkps://example.com" ]
+17
View File
@@ -0,0 +1,17 @@
appenders:
stdout_appender:
kind: console
encoder:
pattern: "{h({d(%Y-%m-%d %H:%M:%S)(utc)} - {l}: {m}{n})}"
file_appender:
kind: file
path: "/tmp/husk.log"
encoder:
pattern: "{h({d(%Y-%m-%d %H:%M:%S)(utc)} - {l}: {m}{n})}"
root:
level: debug
appenders:
- file_appender
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/bash
DOMAIN=$1
IP=$2
if [ "$DOMAIN" == "" ] ; then
echo "USAGE: $0 DOMAIN IP"
exit 1
fi
if [ "$IP" == "" ] ; then
echo "USAGE: $0 DOMAIN IP"
exit 1
fi
/usr/bin/gen_tls_cert.sh $DOMAIN
/usr/bin/gen_hockeypuck_conf.sh $DOMAIN $IP
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/bash
DOMAIN=$1
IP=$2
CERTS=/etc/local_certs
DIR=/etc/hockeypuck.d
CONF_NAME=hockeypuck_
if [ "$DOMAIN" == "" ] ; then
echo "USAGE: $0 DOMAIN IP"
exit 1
fi
if [ "$IP" == "" ] ; then
echo "USAGE: $0 DOMAIN IP"
exit 1
fi
if [ ! -d $DIR ] ; then
mkdir -p $DIR
fi
DB_NAME=$(echo $DOMAIN | tr -d '.-')
cat << EO_CONF > $DIR/$CONF_NAME$DOMAIN.conf
[hockeypuck]
loglevel="INFO"
logfile="/tmp/hockeypuck_$DOMAIN.log"
indexTemplate="/var/www/templates/index.html.tmpl"
vindexTemplate="/var/www/templates/index.html.tmpl"
statsTemplate="/var/www/templates/stats.html.tmpl"
webroot="/var/www/"
[hockeypuck.hkp]
bind="$IP:11371"
[hockeypuck.hkps]
bind="$IP:443"
logRequestDetails=false
cert="$CERTS/$DOMAIN.crt"
key="$CERTS/$DOMAIN.key"
[hockeypuck.openpgp.db]
driver="postgres-jsonb"
dsn="database=hkp_$DB_NAME host=/var/run/postgresql port=5432 sslmode=disable"
[hockeypuck.conflux.recon.leveldb]
path="/tmp/${DOMAIN}_recon.db"
EO_CONF
@@ -0,0 +1,44 @@
#!/usr/bin/bash
export SEQUOIA_HOME=$(mktemp -d)
export TMP=$(mktemp -d)
export SEQUOIA_TARGET=/tmp/sq_home
mkdir -p $SEQUOIA_TARGET
# create keys
ALICE_FPR=$(sq key generate --own-key --without-password --name "Alice" --email "alice@example.com" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2)
BOB_FPR=$(sq key generate --own-key --without-password --name "Bob" --email "bob@example.com" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2)
CAROL_FPR=$(sq key generate --own-key --without-password --name "Carol" --email "carol@example.com" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2)
DAVE_FPR=$(sq key generate --own-key --without-password --name "Dave" --email "dave@example.com" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2)
CA_FPR=$(sq key generate --own-key --without-password --name "CA" --email "ca@example.com" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2)
# vouch for bob
sq pki vouch add --certifier $CA_FPR --cert $BOB_FPR --email "bob@example.com"
# vouch for carol
sq pki vouch add --certifier $CA_FPR --cert $CAROL_FPR --email "carol@example.com"
# export
sq key export --cert $ALICE_FPR > $TMP/alice_sk.pgp
sq key export --cert $CA_FPR > $TMP/ca_sk.pgp
# only certs
sq cert export --cert $BOB_FPR > $TMP/bob_pk.pgp
sq network keyserver publish --server hkps://example.com --cert $CAROL_FPR
sq network keyserver publish --server hkps://example.com --cert $DAVE_FPR
# create store
export SEQUOIA_HOME=$SEQUOIA_TARGET
sq key import $TMP/alice_sk.pgp
sq key import $TMP/ca_sk.pgp
sq cert import $TMP/bob_pk.pgp
sq pki link authorize --unconstrained --cert $CA_FPR --all --domain example.com
# Outcome:
# ca@example.com is authenticated with an unlimited trust depth and a restriction for domain example.com
# alice@example.com has a local key, but it is not authenticated
# bob@example.com has a local cert, which is certified by ca@example.com
# carol@example.com is published on a keyserver and certified by ca@example.com
# dave@example.com is published on a keyserver but uncertified
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/bash
DOMAIN=$1
DIR=/etc/local_certs
USER=hockeypuck
if [ "$DOMAIN" == "" ] ; then
echo "USAGE: $0 DOMAIN"
exit 1
fi
if [ ! -d $DIR ] ; then
mkdir -p $DIR
fi
echo "create key material for $DOMAIN ..."
openssl req -newkey rsa:2048 -keyout $DIR/$DOMAIN.key -nodes -x509 -days 365 -out $DIR/$DOMAIN.crt -subj "/C=DE/ST=HH/L=Earth/O=CompanyName/OU=IT/CN=$DOMAIN/emailAddress=email@example.com"
chown $USER:$USER $DIR/$DOMAIN.key
cp $DIR/$DOMAIN.crt /usr/share/ca-certificates/
echo $DOMAIN.crt >> /etc/ca-certificates.conf
# dont forget to run `update-ca-certificates` after all DOMAINs are done
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/bash
BRANCH=$1
## create domain
echo "127.0.0.1 " $(hostname) > /etc/hosts
echo "127.0.0.2 example.com" >> /etc/hosts
## build milter
if [ -f /opt/husk/mapped/husk ] ; then
HUSK_BIN=/opt/husk/mapped/husk
else
cd /opt/husk
git clone https://gitlab.com/husk-project/husk-milter.git
cd husk-milter
if [ "$BRANCH" != "" ] ; then
git checkout $BRANCH
fi
cargo build --release
HUSK_BIN=/opt/husk/husk-milter/target/release/husk
fi
## start rsyslogd
/usr/sbin/rsyslogd -n -iNONE &
## start postfix
sudo postfix start
## start and prepare postgres
DOMAINS="example.com"
sudo /etc/init.d/postgresql start
echo "create user hockeypuck;" | sudo -u postgres psql -d template1 -U postgres
for domain in $DOMAINS ; do
DB_NAME=$(echo $domain | tr -d '.-')
echo "
create database hkp_$DB_NAME with owner 'hockeypuck';
GRANT ALL PRIVILEGES ON DATABASE hkp_$DB_NAME to hockeypuck;
" | sudo -u postgres psql -d template1 -U postgres
done
#
# start hockeypuck
#
echo "start hockeypuck"
for domain in $DOMAINS ; do
echo " $domain..."
sudo -u hockeypuck hockeypuck --config /etc/hockeypuck.d/hockeypuck_$domain.conf 2>&1 > /tmp/hockeypuck_$domain.out &
done
## create sequoia_home
bash /usr/bin/gen_sq_home.sh
## start milter
cd /opt/husk
$HUSK_BIN config.toml &
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/bash
TEST_DIR=$1
if [ "$TEST_DIR" == "" ] ; then
echo "USAGE: $0 DIRECTORY_WITH_TESTS"
exit 1
fi
for TEST in $TEST_DIR/* ; do
if bash $TEST ; then
echo "$TEST: success"
else
echo "$TEST: failure"
exit 1
fi
done
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/bash
echo "test message" | sendmail -t root@example.com
sleep 1
## simple test if mail passes
if [ -f /var/mail/root ] ; then
echo "success"
rm /var/mail/root
else
echo "failure"
exit 1
fi
## test encryption
## bob@example.com has an authenticated certificate in
## the cert store
echo "test message" | sendmail -t bob@example.com
sleep 1
if [ -f /var/mail/bob ] ; then
if grep -q "BEGIN PGP MESSAGE" /var/mail/bob ; then
echo "encryption test: success"
else
echo "encryption test: failure"
exit 1
fi
else
echo "failure: mail didn't pass"
exit 1
fi
## test encryption 2
## alice@example.com has a certificate in the cert store, but
## it is not authenticated -> this should fail
echo "test message" | sendmail -t alice@example.com
sleep 1
if [ -f /var/mail/alice ] ; then
if grep -q "BEGIN PGP MESSAGE" /var/mail/alice ; then
echo "encryption test 2: failure"
exit 1
else
echo "encryption test 2: success"
fi
else
echo "failure: mail didn't pass"
exit 1
fi