-
Notifications
You must be signed in to change notification settings - Fork 108
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
with tls with acme (with pebble, a small acme server for testing), and with pregenerated keys/certs. the two mox instances are configured on their own domain. we launch a separate test container that connects to the first, submits a message for delivery to the second. we check if the message is delivered with an imap connection and the idle command.
- Loading branch information
Showing
34 changed files
with
595 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,8 @@ default: build | |
build: | ||
# build early to catch syntax errors | ||
CGO_ENABLED=0 go build | ||
CGO_ENABLED=0 go vet -tags integration ./... | ||
CGO_ENABLED=0 go vet -tags quickstart ./... | ||
CGO_ENABLED=0 go vet -tags quickstart quickstart_test.go | ||
./gendoc.sh | ||
(cd http && CGO_ENABLED=0 go run ../vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/*.go -adjust-function-names none Admin) >http/adminapi.json | ||
(cd http && CGO_ENABLED=0 go run ../vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/*.go -adjust-function-names none Account) >http/accountapi.json | ||
|
@@ -24,6 +25,8 @@ test-upgrade: | |
check: | ||
staticcheck ./... | ||
staticcheck -tags integration | ||
staticcheck -tags quickstart | ||
GOARCH=386 CGO_ENABLED=0 go vet -tags integration ./... | ||
|
||
# having "err" shadowed is common, best to not have others | ||
check-shadow: | ||
|
@@ -43,8 +46,15 @@ fuzz: | |
go test -fuzz FuzzParseRecord -fuzztime 5m ./tlsrpt | ||
go test -fuzz FuzzParseMessage -fuzztime 5m ./tlsrpt | ||
|
||
test-integration: | ||
docker-compose -f docker-compose-integration.yml build --no-cache --pull moxmail | ||
-rm -r testdata/integration/data | ||
docker-compose -f docker-compose-integration.yml run moxmail sh -c 'CGO_ENABLED=0 go test -tags integration' | ||
docker-compose -f docker-compose-integration.yml down | ||
|
||
# like test-integration, but in separate steps | ||
integration-build: | ||
docker-compose -f docker-compose-integration.yml build --no-cache moxmail | ||
docker-compose -f docker-compose-integration.yml build --no-cache --pull moxmail | ||
|
||
integration-start: | ||
-rm -r testdata/integration/data | ||
|
@@ -55,15 +65,27 @@ integration-start: | |
integration-test: | ||
CGO_ENABLED=0 go test -tags integration | ||
|
||
|
||
test-quickstart: | ||
docker image build --pull -f Dockerfile -t mox_quickstart_moxmail . | ||
docker image build --pull -f testdata/quickstart/Dockerfile.test -t mox_quickstart_test testdata/quickstart | ||
-rm -rf testdata/quickstart/moxacmepebble/data | ||
-rm -rf testdata/quickstart/moxmail2/data | ||
-rm -f testdata/quickstart/tmp-pebble-ca.pem | ||
MOX_UID=$$(id -u) docker-compose -f docker-compose-quickstart.yml run test | ||
docker-compose -f docker-compose-quickstart.yml down --timeout 1 | ||
|
||
|
||
imaptest-build: | ||
-docker-compose -f docker-compose-imaptest.yml build --no-cache mox | ||
-docker-compose -f docker-compose-imaptest.yml build --no-cache --pull mox | ||
|
||
imaptest-run: | ||
-rm -r testdata/imaptest/data | ||
mkdir testdata/imaptest/data | ||
docker-compose -f docker-compose-imaptest.yml run --entrypoint /usr/local/bin/imaptest imaptest host=mox port=1143 [email protected] pass=testtest mbox=imaptest.mbox | ||
docker-compose -f docker-compose-imaptest.yml down | ||
|
||
|
||
fmt: | ||
go fmt ./... | ||
gofmt -w -s *.go */*.go | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
version: '3.7' | ||
services: | ||
# We run quickstart_test.go from this container, it connects to both mox instances. | ||
test: | ||
hostname: test.mox1.example | ||
image: mox_quickstart_test | ||
# We add our cfssl-generated CA (which is in the repo) and acme pebble CA | ||
# (generated each time pebble starts) to the list of trusted CA's, so the TLS | ||
# dials in quickstart_test.go succeed. | ||
command: ["sh", "-c", "set -ex; cat /quickstart/tmp-pebble-ca.pem /quickstart/tls/ca.pem >>/etc/ssl/certs/ca-certificates.crt; go test -tags quickstart"] | ||
volumes: | ||
- ./.go:/.go | ||
- ./testdata/quickstart/resolv.conf:/etc/resolv.conf | ||
- ./testdata/quickstart:/quickstart | ||
- .:/mox | ||
environment: | ||
GOCACHE: /.go/.cache/go-build | ||
depends_on: | ||
dns: | ||
condition: service_healthy | ||
# moxmail2 depends on moxacmepebble, we connect to both. | ||
moxmail2: | ||
condition: service_healthy | ||
networks: | ||
mailnet1: | ||
ipv4_address: 172.28.1.50 | ||
|
||
# First mox instance that uses ACME with pebble. | ||
moxacmepebble: | ||
hostname: moxacmepebble.mox1.example | ||
domainname: mox1.example | ||
image: mox_quickstart_moxmail | ||
environment: | ||
MOX_UID: "${MOX_UID}" | ||
command: ["sh", "-c", "/quickstart/moxacmepebble.sh"] | ||
volumes: | ||
- ./testdata/quickstart/resolv.conf:/etc/resolv.conf | ||
- ./testdata/quickstart:/quickstart | ||
healthcheck: | ||
test: netstat -nlt | grep ':25 ' | ||
interval: 1s | ||
timeout: 1s | ||
retries: 10 | ||
depends_on: | ||
dns: | ||
condition: service_healthy | ||
acmepebble: | ||
condition: service_healthy | ||
networks: | ||
mailnet1: | ||
ipv4_address: 172.28.1.10 | ||
|
||
# Second mox instance, with TLS cert/keys from files. | ||
moxmail2: | ||
hostname: moxmail2.mox2.example | ||
domainname: mox2.example | ||
image: mox_quickstart_moxmail | ||
environment: | ||
MOX_UID: "${MOX_UID}" | ||
command: ["sh", "-c", "/quickstart/moxmail2.sh"] | ||
volumes: | ||
- ./testdata/quickstart/resolv.conf:/etc/resolv.conf | ||
- ./testdata/quickstart:/quickstart | ||
healthcheck: | ||
test: netstat -nlt | grep ':25 ' | ||
interval: 1s | ||
timeout: 1s | ||
retries: 10 | ||
depends_on: | ||
dns: | ||
condition: service_healthy | ||
acmepebble: | ||
condition: service_healthy | ||
# moxacmepebble creates tmp-pebble-ca.pem, needed by moxmail2 to trust the certificates offered by moxacmepebble. | ||
moxacmepebble: | ||
condition: service_healthy | ||
networks: | ||
mailnet1: | ||
ipv4_address: 172.28.1.20 | ||
|
||
dns: | ||
hostname: dns.example | ||
build: | ||
dockerfile: Dockerfile.dns | ||
# todo: figure out how to build from dockerfile with empty context without creating empty dirs in file system. | ||
context: testdata/quickstart | ||
volumes: | ||
- ./testdata/quickstart/resolv.conf:/etc/resolv.conf | ||
- ./testdata/quickstart:/quickstart | ||
# We start with a base example.zone, but moxacmepebble appends its records, | ||
# followed by moxmail2. They restart unbound after appending records. | ||
command: ["sh", "-c", "set -ex; ls -l /etc/resolv.conf; chmod o+r /etc/resolv.conf; install -m 640 -o unbound /quickstart/unbound.conf /etc/unbound/; chmod 755 /quickstart; chmod 644 /quickstart/*.zone; cp /quickstart/example.zone /quickstart/example-quickstart.zone; ls -ld /quickstart /quickstart/reverse.zone; unbound -d -p -v"] | ||
healthcheck: | ||
test: netstat -nlu | grep '172.28.1.30:53 ' | ||
interval: 1s | ||
timeout: 1s | ||
retries: 10 | ||
networks: | ||
mailnet1: | ||
ipv4_address: 172.28.1.30 | ||
|
||
# pebble is a small acme server useful for testing. It creates a new CA | ||
# certificate each time it starts, so we go through some trouble to configure the | ||
# certificate in moxacmepebble and moxmail2. | ||
acmepebble: | ||
hostname: acmepebble.example | ||
image: docker.io/letsencrypt/pebble:v2.3.1@sha256:fc5a537bf8fbc7cc63aa24ec3142283aa9b6ba54529f86eb8ff31fbde7c5b258 | ||
volumes: | ||
- ./testdata/quickstart/resolv.conf:/etc/resolv.conf | ||
- ./testdata/quickstart:/quickstart | ||
command: ["sh", "-c", "set -ex; mount; ls -l /etc/resolv.conf; chmod o+r /etc/resolv.conf; pebble -config /quickstart/pebble-config.json"] | ||
ports: | ||
- 14000:14000 # ACME port | ||
- 15000:15000 # Management port | ||
healthcheck: | ||
test: netstat -nlt | grep ':14000 ' | ||
interval: 1s | ||
timeout: 1s | ||
retries: 10 | ||
depends_on: | ||
dns: | ||
condition: service_healthy | ||
networks: | ||
mailnet1: | ||
ipv4_address: 172.28.1.40 | ||
|
||
networks: | ||
mailnet1: | ||
driver: bridge | ||
ipam: | ||
driver: default | ||
config: | ||
- subnet: "172.28.1.0/24" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
//go:build quickstart | ||
|
||
// Run this using docker-compose.yml, see Makefile. | ||
|
||
package main | ||
|
||
import ( | ||
"bytes" | ||
"crypto/tls" | ||
"encoding/base64" | ||
"fmt" | ||
"os" | ||
"strings" | ||
"testing" | ||
"time" | ||
|
||
"github.com/mjl-/mox/imapclient" | ||
"github.com/mjl-/mox/mlog" | ||
"github.com/mjl-/mox/mox-" | ||
"github.com/mjl-/mox/smtpclient" | ||
) | ||
|
||
var xlog = mlog.New("quickstart") | ||
|
||
func tcheck(t *testing.T, err error, msg string) { | ||
t.Helper() | ||
if err != nil { | ||
t.Fatalf("%s: %s", msg, err) | ||
} | ||
} | ||
|
||
func TestDeliver(t *testing.T) { | ||
mlog.Logfmt = true | ||
|
||
// smtpclient uses the hostname for outgoing connections. | ||
var err error | ||
mox.Conf.Static.HostnameDomain.ASCII, err = os.Hostname() | ||
tcheck(t, err, "hostname") | ||
|
||
// Deliver submits a message over submissions, and checks with imap idle if the | ||
// message is received by the destination mail server. | ||
deliver := func(desthost, mailfrom, password, rcptto, imaphost, imapuser, imappassword string) { | ||
t.Helper() | ||
|
||
// Connect to IMAP, execute IDLE command, which will return on deliver message. | ||
// TLS certificates work because the container has the CA certificates configured. | ||
imapconn, err := tls.Dial("tcp", imaphost+":993", nil) | ||
tcheck(t, err, "dial imap") | ||
defer imapconn.Close() | ||
|
||
imaperr := make(chan error, 1) | ||
go func() { | ||
go func() { | ||
x := recover() | ||
if x == nil { | ||
return | ||
} | ||
imaperr <- x.(error) | ||
}() | ||
xcheck := func(err error, format string) { | ||
if err != nil { | ||
panic(fmt.Errorf("%s: %w", format, err)) | ||
} | ||
} | ||
|
||
imapc, err := imapclient.New(imapconn, false) | ||
xcheck(err, "new imapclient") | ||
|
||
_, _, err = imapc.Login(imapuser, imappassword) | ||
xcheck(err, "imap login") | ||
|
||
_, _, err = imapc.Select("Inbox") | ||
xcheck(err, "imap select inbox") | ||
|
||
err = imapc.Commandf("", "idle") | ||
xcheck(err, "write imap idle command") | ||
_, _, _, err = imapc.ReadContinuation() | ||
xcheck(err, "read imap continuation") | ||
|
||
done := make(chan error) | ||
go func() { | ||
defer func() { | ||
x := recover() | ||
if x != nil { | ||
done <- fmt.Errorf("%v", x) | ||
} | ||
}() | ||
untagged, err := imapc.ReadUntagged() | ||
if err != nil { | ||
done <- err | ||
return | ||
} | ||
if _, ok := untagged.(imapclient.UntaggedExists); !ok { | ||
done <- fmt.Errorf("expected imapclient.UntaggedExists, got %#v", untagged) | ||
return | ||
} | ||
done <- nil | ||
}() | ||
|
||
period := 30 * time.Second | ||
timer := time.NewTimer(period) | ||
defer timer.Stop() | ||
select { | ||
case err = <-done: | ||
case <-timer.C: | ||
err = fmt.Errorf("nothing within %v", period) | ||
} | ||
xcheck(err, "waiting for imap untagged repsonse to idle") | ||
imaperr <- nil | ||
}() | ||
|
||
conn, err := tls.Dial("tcp", desthost+":465", nil) | ||
tcheck(t, err, "dial submission") | ||
defer conn.Close() | ||
|
||
msg := fmt.Sprintf(`From: <%s> | ||
To: <%s> | ||
Subject: test message | ||
This is the message. | ||
`, mailfrom, rcptto) | ||
msg = strings.ReplaceAll(msg, "\n", "\r\n") | ||
auth := bytes.Join([][]byte{nil, []byte(mailfrom), []byte(password)}, []byte{0}) | ||
authLine := fmt.Sprintf("AUTH PLAIN %s", base64.StdEncoding.EncodeToString(auth)) | ||
c, err := smtpclient.New(mox.Context, xlog, conn, smtpclient.TLSSkip, desthost, authLine) | ||
tcheck(t, err, "smtp hello") | ||
err = c.Deliver(mox.Context, mailfrom, rcptto, int64(len(msg)), strings.NewReader(msg), false, false) | ||
tcheck(t, err, "deliver with smtp") | ||
err = c.Close() | ||
tcheck(t, err, "close smtpclient") | ||
|
||
err = <-imaperr | ||
tcheck(t, err, "imap idle") | ||
} | ||
|
||
xlog.Print("submitting email to moxacmepebble, waiting for imap notification at moxmail2, takes time because first-time sender") | ||
t0 := time.Now() | ||
deliver("moxacmepebble.mox1.example", "[email protected]", "accountpass1234", "[email protected]", "moxmail2.mox2.example", "[email protected]", "accountpass4321") | ||
xlog.Print("success", mlog.Field("duration", time.Since(t0))) | ||
|
||
xlog.Print("submitting email to moxmail2, waiting for imap notification at moxacmepebble, takes time because first-time sender") | ||
t0 = time.Now() | ||
deliver("moxmail2.mox2.example", "[email protected]", "accountpass4321", "[email protected]", "moxacmepebble.mox1.example", "[email protected]", "accountpass1234") | ||
xlog.Print("success", mlog.Field("duration", time.Since(t0))) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
FROM alpine:3.17 | ||
RUN apk add unbound bind-tools mailx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
FROM golang:1-alpine AS build | ||
WORKDIR /mox | ||
RUN apk add make bind-tools bash unbound curl | ||
env GOPROXY=off |
Oops, something went wrong.