1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-18 14:47:03 +00:00

test: Add a new local end-to-end test

This patch introduces a new directory, test/, which contains a simple local
end-to-end test which runs a chasquid binary and uses msmtp to send an email,
which is delivered locally.

As it's the first one, it adds a bunch of common infrastructure to simplify
writing these kinds of tests.

More end-to-end tests will follow, and it's expected that the common
infrastructure will also change significantly to accomodate their needs.
This commit is contained in:
Alberto Bertogli
2016-07-22 01:52:27 +01:00
parent 92d16a0ca9
commit 92a88bd06f
10 changed files with 343 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
address: ":1025"
monitoring_address: ":1099"
mail_delivery_agent_bin: "test-mda"
mail_delivery_agent_args: "%user%@%domain%"

View File

@@ -0,0 +1,2 @@
#chasquid-userdb-v1
user SCRYPT@n:14,r:8,p:1,l:32,r00XqNmRkV505R2X6KT8+Q== aAiBBIVNNzmDXwxLLdJezFuxGtc2/wcHsy3FiOMAH4c=

View File

@@ -0,0 +1,4 @@
Subject: Prueba desde el test
Crece desde el test el futuro
Crece desde el test

View File

@@ -0,0 +1 @@
testserver localhost

View File

@@ -0,0 +1,21 @@
account default
host testserver
port 1025
tls on
tls_trust_file config/domains/testserver/cert.pem
from user@testserver
auth on
user user@testserver
password secretpassword
account baduser : default
user unknownuser@testserver
password secretpassword
account badpasswd : default
user user@testserver
password badsecretpassword

29
test/t-simple_local/run.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/bash
set -e
. $(dirname ${0})/../util/lib.sh
init
generate_certs_for testserver
chasquid -v=2 --log_dir=.logs --config_dir=config &
wait_until_ready 1025
run_msmtp someone@testserver < content
wait_for_file .mail/someone@testserver
mail_diff content .mail/someone@testserver
if run_msmtp -a baduser someone@testserver < content 2> /dev/null; then
echo "ERROR: successfully sent an email with a bad password"
exit 1
fi
if run_msmtp -a badpasswd someone@testserver < content 2> /dev/null; then
echo "ERROR: successfully sent an email with a bad password"
exit 1
fi
success

159
test/util/generate_cert.go Normal file
View File

@@ -0,0 +1,159 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
// Generate a self-signed X.509 certificate for a TLS server. Outputs to
// 'cert.pem' and 'key.pem' and will overwrite existing files.
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"flag"
"fmt"
"log"
"math/big"
"net"
"os"
"strings"
"time"
)
var (
host = flag.String("host", "", "Comma-separated hostnames and IPs to generate a certificate for")
validFrom = flag.String("start-date", "", "Creation date formatted as Jan 1 15:04:05 2011")
validFor = flag.Duration("duration", 365*24*time.Hour, "Duration that certificate is valid for")
isCA = flag.Bool("ca", false, "whether this cert should be its own Certificate Authority")
rsaBits = flag.Int("rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set")
ecdsaCurve = flag.String("ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521")
)
func publicKey(priv interface{}) interface{} {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &k.PublicKey
case *ecdsa.PrivateKey:
return &k.PublicKey
default:
return nil
}
}
func pemBlockForKey(priv interface{}) *pem.Block {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
case *ecdsa.PrivateKey:
b, err := x509.MarshalECPrivateKey(k)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err)
os.Exit(2)
}
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
default:
return nil
}
}
func main() {
flag.Parse()
if len(*host) == 0 {
log.Fatalf("Missing required --host parameter")
}
var priv interface{}
var err error
switch *ecdsaCurve {
case "":
priv, err = rsa.GenerateKey(rand.Reader, *rsaBits)
case "P224":
priv, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
case "P256":
priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
case "P384":
priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
case "P521":
priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
default:
fmt.Fprintf(os.Stderr, "Unrecognized elliptic curve: %q", *ecdsaCurve)
os.Exit(1)
}
if err != nil {
log.Fatalf("failed to generate private key: %s", err)
}
var notBefore time.Time
if len(*validFrom) == 0 {
notBefore = time.Now()
} else {
notBefore, err = time.Parse("Jan 2 15:04:05 2006", *validFrom)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse creation date: %s\n", err)
os.Exit(1)
}
}
notAfter := notBefore.Add(*validFor)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
log.Fatalf("failed to generate serial number: %s", err)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Acme Co"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
hosts := strings.Split(*host, ",")
for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
}
if *isCA {
template.IsCA = true
template.KeyUsage |= x509.KeyUsageCertSign
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
if err != nil {
log.Fatalf("Failed to create certificate: %s", err)
}
certOut, err := os.Create("cert.pem")
if err != nil {
log.Fatalf("failed to open cert.pem for writing: %s", err)
}
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
certOut.Close()
keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Fatalf("failed to open key.pem for writing:", err)
return
}
pem.Encode(keyOut, pemBlockForKey(priv))
keyOut.Close()
}

74
test/util/lib.sh Normal file
View File

@@ -0,0 +1,74 @@
# Library to write the shell scripts in the tests.
function init() {
if [ "$V" == "1" ]; then
set -v
fi
export TBASE="$(realpath `dirname ${0}`)"
cd ${TBASE}
export UTILDIR="$(realpath ${TBASE}/../util/)"
# Remove the directory where test-mda will deliver mail, so previous
# runs don't interfere with this one.
rm -rf .mail
# Set traps to kill our subprocesses when we exit (for any reason).
# https://stackoverflow.com/questions/360201/
trap "exit" INT TERM
trap "kill 0" EXIT
}
function generate_cert() {
go run ${UTILDIR}/generate_cert.go "$@"
}
function chasquid() {
# HOSTALIASES: so we "fake" hostnames.
# PATH: so chasquid can call test-mda without path issues.
HOSTALIASES=${TBASE}/hosts \
PATH=${UTILDIR}:${PATH} \
go run ${TBASE}/../../chasquid.go "$@"
}
function run_msmtp() {
# msmtp will check that the rc file is only user readable.
chmod 600 msmtprc
HOSTALIASES=${TBASE}/hosts \
msmtp -C msmtprc "$@"
}
function mail_diff() {
${UTILDIR}/mail_diff "$@"
}
function success() {
echo "SUCCESS"
}
# Wait until there's something listening on the given port.
function wait_until_ready() {
PORT=$1
while ! nc -z localhost $PORT; do
sleep 0.1
done
}
# Wait for the given file to exist.
function wait_for_file() {
while ! [ -e ${1} ]; do
sleep 0.1
done
}
# Generate certs for the given domain.
function generate_certs_for() {
mkdir -p config/domains/${1}
(
cd config/domains/${1}
generate_cert -ca -duration=1h -host=${1}
)
}

34
test/util/mail_diff Executable file
View File

@@ -0,0 +1,34 @@
#!/usr/bin/env python
import difflib
import email.parser
import mailbox
import sys
f1, f2 = sys.argv[1:3]
expected = email.parser.Parser().parse(open(f1))
mbox = mailbox.mbox(f2, create=False)
msg = mbox[0]
diff = False
for h, val in expected.items():
if h not in msg:
print("Header missing: %r" % h)
diff = True
continue
if msg[h] != val:
print("Header %r differs: %r != %r" % (h, val, msg[h]))
diff = True
if expected.get_payload() != msg.get_payload():
diff = True
exp = expected.get_payload().splitlines()
got = msg.get_payload().splitlines()
print("Payload differs:")
for l in difflib.ndiff(exp, got):
print(l)
sys.exit(0 if not diff else 1)

14
test/util/test-mda Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
set -e
mkdir -p .mail
# TODO: use flock to lock the file, to prevent atomic writes.
echo "From ${1}" >> .mail/.tmp-${1}
cat >> .mail/.tmp-${1}
X=$?
if [ -e .mail/.tmp-${1} ]; then
mv .mail/.tmp-${1} .mail/${1}
fi
exit $X