Adding wildduck for testing

This commit is contained in:
2023-08-09 21:28:31 +02:00
parent b65ae759c1
commit f32fb8d9a8
10 changed files with 1570 additions and 0 deletions

187
share/wildduck/haraka.tf Normal file
View File

@@ -0,0 +1,187 @@
locals {
haraka-labels = merge(local.common-labels, {
"app.kubernetes.io/component" = "haraka"
})
}
resource "kubectl_manifest" "haraka_deploy" {
yaml_body = <<-EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: "${var.instance}-haraka"
namespace: "${var.namespace}"
labels: ${jsonencode(local.haraka-labels)}
spec:
replicas: 1
selector:
matchLabels: ${jsonencode(local.haraka-labels)}
template:
metadata:
labels: ${jsonencode(local.haraka-labels)}
spec:
securityContext:
fsGroup: 1000
containers:
- name: wildduck
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
image: "${var.images.haraka.registry}/${var.images.haraka.repository}:${var.images.haraka.tag}"
imagePullPolicy: "${var.images.haraka.pullPolicy}"
ports:
- name: smtp
containerPort: 25
protocol: TCP
livenessProbe:
tcpSocket:
port: smtp
initialDelaySeconds: 20
periodSeconds: 30
readinessProbe:
tcpSocket:
port: smtp
initialDelaySeconds: 20
periodSeconds: 30
resources:
{}
volumeMounts:
- name: config
mountPath: /app/config
volumes:
- name: config
configMap:
name: "${var.instance}-haraka"
EOF
}
resource "kubectl_manifest" "haraka_config" {
yaml_body = <<-EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: "${var.instance}-haraka"
namespace: "${var.namespace}"
labels: ${jsonencode(local.haraka-labels)}
data:
me: |-
${var.sub-domain}.${var.domain-name}
host_list: |-
# add hosts in here we want to accept mail for
${var.sub-domain}.${var.domain-name}
${var.domain-name}
${join("\n ",var.additional-domains)}
rspamd.ini: |-
host = ${var.instance}-rspamd.${var.namespace}.svc.cluster.local
port = 11333
add_headers = always
[dkim]
enabled = true
[header]
bar = X-Rspamd-Bar
report = X-Rspamd-Report
score = X-Rspamd-Score
spam = X-Rspamd-Spam
[check]
authenticated=true
private_ip=true
[reject]
spam = false
[soft_reject]
enabled = true
[rmilter_headers]
enabled = true
[spambar]
positive = +
negative = -
neutral = /'
wildduck.yaml: |-
## Connect to a master instance or Redis
redis:
port: 6379
host: "${var.instance}-${var.component}-redis.${var.namespace}.svc"
db: 3
mongo:
# connection string for main messages database
url: 'mongodb://${var.component}:${local.mongo-password}@${var.instance}-${var.component}-mongo-svc.${var.namespace}.svc:27017/wildduck'
## database name or connection string for the users db
#users: "users"
## database name or connection string for the attachments db
#gridfs: "attachments"
## database name or connection string for the outbound queue
sender: 'mongodb://${var.component}:${local.mongo-password}@${var.instance}-${var.component}-mongo-svc.${var.namespace}.svc:27017/zone-mta'
sender:
# Push messages to ZoneMTA queue for delivery
# if `false` then no messages are sent
enabled: true
# which ZoneMTA queue to use by default. This mostly affects forwarded messages
zone: 'default'
# Collection name for GridFS storage
gfs: 'mail'
# Collection name for the queue
# see [dbs].sender option for choosing correct database to use for ZoneMTA queues
# by default the main wildduck database is used
collection: 'zone-queue'
# Hashing secret for loop detection
# Must be shared with wildduck
# If not set then looping is not tracked
loopSecret: '${local.secrets.srs}'
srs:
# must be shared with ZoneMTA SRS config, otherwise messages sent from ZoneMTA are not recognized by Haraka
secret: 'secret value'
attachments:
type: 'gridstore'
bucket: 'attachments'
decodeBase64: true
log:
authlogExpireDays: 30
limits:
windowSize: 3600 # 1 hour
rcptIp: 100 # allowed messages for the same recipient from same IP during window size
rcptWindowSize: 60 # 1 minute
rcpt: 60 # allowed messages for the same recipient during window size
gelf:
enabled: false
component: 'mx'
options:
graylogPort: 12201
graylogHostname: '127.0.0.1'
connection: 'lan'
rspamd:
# do not process forwarding addresses for messages with the following spam score
forwardSkip: 10
# if a message has one of the tags listed here with positive score, the message will be rejected
blacklist:
- DMARC_POLICY_REJECT
# if a message has one of the tags listed here with positive score, the message will be soft rejected
softlist:
- RBL_ZONE
# define special responses
responses:
DMARC_POLICY_REJECT: "Unauthenticated email from {host} is not accepted due to domain's DMARC policy"
RBL_ZONE: '[{host}] was found from Zone RBL'
EOF
}
resource "kubectl_manifest" "haraka_service" {
yaml_body = <<-EOF
apiVersion: v1
kind: Service
metadata:
name: "${var.instance}-haraka"
namespace: "${var.namespace}"
labels: ${jsonencode(local.haraka-labels)}
spec:
type: LoadBalancer
ports:
- port: 25
targetPort: smtp
protocol: TCP
name: smtp
selector: ${jsonencode(local.haraka-labels)}
EOF
}

252
share/wildduck/index.yaml Normal file
View File

@@ -0,0 +1,252 @@
---
apiVersion: vinyl.solidite.fr/v1beta1
kind: Component
category: share
metadata:
name: wildduck
description: null
options:
domain-name:
default: your_company.com
examples:
- your_company.com
type: string
domain:
default: your-company
examples:
- your-company
type: string
issuer:
default: letsencrypt-prod
examples:
- letsencrypt-prod
type: string
ingress-class:
default: traefik
examples:
- traefik
type: string
images:
default:
haraka:
pullPolicy: IfNotPresent
registry: docker.io
repository: sebt3/wildduck-haraka
tag: 3.0.2
rspamd:
pullPolicy: IfNotPresent
registry: docker.io
repository: sebt3/wildduck-rspamd
tag: 3.18.3
webmail:
pullPolicy: IfNotPresent
registry: docker.io
repository: nodemailer/wildduck-webmail
tag: 1.0.1
wildduck:
pullPolicy: IfNotPresent
registry: docker.io
repository: nodemailer/wildduck
tag: 1.39.10
zonemta:
pullPolicy: IfNotPresent
registry: docker.io
repository: sebt3/wildduck-zonemta
tag: 3.4.0
examples:
- haraka:
pullPolicy: IfNotPresent
registry: docker.io
repository: sebt3/wildduck-haraka
tag: 3.0.2
rspamd:
pullPolicy: IfNotPresent
registry: docker.io
repository: sebt3/wildduck-rspamd
tag: 3.18.3
webmail:
pullPolicy: IfNotPresent
registry: docker.io
repository: nodemailer/wildduck-webmail
tag: 1.0.1
wildduck:
pullPolicy: IfNotPresent
registry: docker.io
repository: nodemailer/wildduck
tag: 1.39.10
zonemta:
pullPolicy: IfNotPresent
registry: docker.io
repository: sebt3/wildduck-zonemta
tag: 3.4.0
properties:
haraka:
default:
pullPolicy: IfNotPresent
registry: docker.io
repository: sebt3/wildduck-haraka
tag: 3.0.2
properties:
pullPolicy:
default: IfNotPresent
type: string
registry:
default: docker.io
type: string
repository:
default: sebt3/wildduck-haraka
type: string
tag:
default: 3.0.2
type: string
type: object
operator:
default: null
properties:
pullPolicy:
enum:
- Always
- Never
- IfNotPresent
rspamd:
default:
pullPolicy: IfNotPresent
registry: docker.io
repository: sebt3/wildduck-rspamd
tag: 3.18.3
properties:
pullPolicy:
default: IfNotPresent
type: string
registry:
default: docker.io
type: string
repository:
default: sebt3/wildduck-rspamd
type: string
tag:
default: 3.18.3
type: string
type: object
webmail:
default:
pullPolicy: IfNotPresent
registry: docker.io
repository: nodemailer/wildduck-webmail
tag: 1.0.1
properties:
pullPolicy:
default: IfNotPresent
type: string
registry:
default: docker.io
type: string
repository:
default: nodemailer/wildduck-webmail
type: string
tag:
default: 1.0.1
type: string
type: object
wildduck:
default:
pullPolicy: IfNotPresent
registry: docker.io
repository: nodemailer/wildduck
tag: 1.39.10
properties:
pullPolicy:
default: IfNotPresent
type: string
registry:
default: docker.io
type: string
repository:
default: nodemailer/wildduck
type: string
tag:
default: 1.39.10
type: string
type: object
zonemta:
default:
pullPolicy: IfNotPresent
registry: docker.io
repository: sebt3/wildduck-zonemta
tag: 3.4.0
properties:
pullPolicy:
default: IfNotPresent
type: string
registry:
default: docker.io
type: string
repository:
default: sebt3/wildduck-zonemta
type: string
tag:
default: 3.4.0
type: string
type: object
type: object
redis:
default:
exporter:
enabled: true
image: quay.io/opstree/redis-exporter:v1.44.0
image: quay.io/opstree/redis:v7.0.5
storage: 2Gi
examples:
- exporter:
enabled: true
image: quay.io/opstree/redis-exporter:v1.44.0
image: quay.io/opstree/redis:v7.0.5
storage: 2Gi
properties:
exporter:
default:
enabled: true
image: quay.io/opstree/redis-exporter:v1.44.0
properties:
enabled:
default: true
type: boolean
image:
default: quay.io/opstree/redis-exporter:v1.44.0
type: string
type: object
image:
default: quay.io/opstree/redis:v7.0.5
type: string
storage:
default: 2Gi
type: string
type: object
additional-domains:
default: []
items:
type: string
type: array
sub-domain:
default: mail
examples:
- mail
type: string
dependencies:
- dist: null
category: dbo
component: mongo
- dist: null
category: dbo
component: redis
- dist: null
category: core
component: secret-generator
providers:
kubernetes: true
authentik: null
kubectl: true
postgresql: null
restapi: null
http: null
tfaddtype: null

76
share/wildduck/ingress.tf Normal file
View File

@@ -0,0 +1,76 @@
locals {
dns-names = ["${var.sub-domain}.${var.domain-name}"]
cert-names = concat(local.dns-names, ["${var.domain-name}"])
middlewares = ["${var.instance}-https"]
service = {
"name" = "${var.instance}-webmail"
"port" = {
"number" = 80
}
}
rules = [ for v in local.dns-names : {
"host" = "${v}"
"http" = {
"paths" = [{
"backend" = {
"service" = local.service
}
"path" = "/"
"pathType" = "Prefix"
}]
}
}]
}
resource "kubectl_manifest" "prj_certificate" {
yaml_body = <<-EOF
apiVersion: "cert-manager.io/v1"
kind: "Certificate"
metadata:
name: "${var.instance}"
namespace: "${var.namespace}"
labels: ${jsonencode(local.webmail-labels)}
spec:
secretName: "${var.instance}-cert"
dnsNames: ${jsonencode(local.cert-names)}
issuerRef:
name: "${var.issuer}"
kind: "ClusterIssuer"
group: "cert-manager.io"
EOF
}
resource "kubectl_manifest" "prj_https_redirect" {
yaml_body = <<-EOF
apiVersion: "traefik.containo.us/v1alpha1"
kind: "Middleware"
metadata:
name: "${var.instance}-https"
namespace: "${var.namespace}"
labels: ${jsonencode(local.webmail-labels)}
spec:
redirectScheme:
scheme: "https"
permanent: true
EOF
}
resource "kubectl_manifest" "prj_ingress" {
force_conflicts = true
yaml_body = <<-EOF
apiVersion: "networking.k8s.io/v1"
kind: "Ingress"
metadata:
name: "${var.instance}"
namespace: "${var.namespace}"
labels: ${jsonencode(local.webmail-labels)}
annotations:
"traefik.ingress.kubernetes.io/router.middlewares": "${join(",", [for m in local.middlewares : format("%s-%s@kubernetescrd", var.namespace, m)])}"
spec:
ingressClassName: "${var.ingress-class}"
rules: ${jsonencode(local.rules)}
tls:
- hosts: ${jsonencode(local.dns-names)}
secretName: "${var.instance}-cert"
EOF
}

101
share/wildduck/mongo.tf Normal file
View File

@@ -0,0 +1,101 @@
locals {
mongo-labels = merge(local.common-labels, {
"app.kubernetes.io/component" = "mongo"
})
}
resource "kubectl_manifest" "prj_mongo_secret" {
ignore_fields = ["metadata.annotations"]
yaml_body = <<-EOF
apiVersion: "secretgenerator.mittwald.de/v1alpha1"
kind: "StringSecret"
metadata:
name: "${var.instance}-${var.component}-mongo"
namespace: "${var.namespace}"
labels: ${jsonencode(local.mongo-labels)}
spec:
forceRegenerate: false
fields:
- fieldName: "password"
length: "16"
EOF
}
data "kubernetes_secret_v1" "prj_mongo_secret" {
depends_on = [ kubectl_manifest.prj_mongo_secret ]
metadata {
name = "${var.instance}-${var.component}-mongo"
namespace = var.namespace
}
}
locals {
mongo-password = data.kubernetes_secret_v1.prj_mongo_secret.data["password"]
}
resource "kubectl_manifest" "prj_mongo" {
yaml_body = <<-EOF
apiVersion: mongodbcommunity.mongodb.com/v1
kind: MongoDBCommunity
metadata:
name: "${var.instance}-${var.component}-mongo"
namespace: "${var.namespace}"
labels: ${jsonencode(local.mongo-labels)}
spec:
members: 1
type: ReplicaSet
version: "4.4.0"
security:
authentication:
modes: ["SCRAM"]
users:
- db: ${var.component}
name: ${var.component}
passwordSecretRef:
name: "${var.instance}-${var.component}-mongo"
roles:
- db: ${var.component}
name: readWrite
scramCredentialsSecretName: "${var.instance}-${var.component}-mongo-scram"
EOF
}
resource "kubectl_manifest" "prj_mongo_sa" {
yaml_body = <<-EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: "mongodb-database"
namespace: "${var.namespace}"
labels: ${jsonencode(local.mongo-labels)}
EOF
}
resource "kubectl_manifest" "prj_mongo_role" {
yaml_body = <<-EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: "mongodb-database"
namespace: "${var.namespace}"
labels: ${jsonencode(local.mongo-labels)}
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["patch", "delete", "get"]
EOF
}
resource "kubectl_manifest" "prj_mongo_rb" {
yaml_body = <<-EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: "mongodb-database"
namespace: "${var.namespace}"
labels: ${jsonencode(local.mongo-labels)}
subjects:
- kind: ServiceAccount
name: mongodb-database
roleRef:
kind: Role
name: mongodb-database
apiGroup: rbac.authorization.k8s.io
EOF
}

32
share/wildduck/redis.tf Normal file
View File

@@ -0,0 +1,32 @@
locals {
redis-labels = merge(local.common-labels, {
"app.kubernetes.io/component" = "redis"
})
}
resource "kubectl_manifest" "prj_redis" {
yaml_body = <<-EOF
apiVersion: "redis.redis.opstreelabs.in/v1beta1"
kind: "Redis"
metadata:
name: "${var.instance}-${var.component}-redis"
namespace: "${var.namespace}"
labels: ${jsonencode(local.redis-labels)}
spec:
kubernetesConfig:
image: "${var.redis.image}"
imagePullPolicy: "IfNotPresent"
storage:
volumeClaimTemplate:
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: "${var.redis.storage}"
redisExporter:
enabled: ${var.redis.exporter.enabled}
image: "${var.redis.exporter.image}"
securityContext:
runAsUser: 1000
fsGroup: 1000
EOF
}

106
share/wildduck/rspamd.tf Normal file
View File

@@ -0,0 +1,106 @@
locals {
rspamd-labels = merge(local.common-labels, {
"app.kubernetes.io/component" = "rspamd"
})
}
resource "kubectl_manifest" "rspamd_deploy" {
yaml_body = <<-EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: "${var.instance}-rspamd"
namespace: "${var.namespace}"
labels: ${jsonencode(local.rspamd-labels)}
spec:
replicas: 1
selector:
matchLabels: ${jsonencode(local.rspamd-labels)}
template:
metadata:
labels: ${jsonencode(local.rspamd-labels)}
spec:
securityContext:
fsGroup: 101
containers:
- name: wildduck
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 100
image: "${var.images.rspamd.registry}/${var.images.rspamd.repository}:${var.images.rspamd.tag}"
imagePullPolicy: "${var.images.rspamd.pullPolicy}"
ports:
- name: rspamd
containerPort: 11333
protocol: TCP
resources:
{}
volumeMounts:
- name: config
mountPath: /app/rspamd/worker-normal.conf
subPath: worker-normal.conf
- name: config
mountPath: /etc/rspamd/override.d/dmarc.conf
subPath: dmarc.conf
- name: config
mountPath: /etc/rspamd/override.d/redis.conf
subPath: redis.conf
volumes:
- name: config
configMap:
name: "${var.instance}-rspamd"
EOF
}
resource "kubectl_manifest" "rspamd_config" {
yaml_body = <<-EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: "${var.instance}-rspamd"
namespace: "${var.namespace}"
labels: ${jsonencode(local.rspamd-labels)}
data:
worker-normal.conf: |-
# Included from top-level .conf file
worker "normal" {
bind_socket = "*:11333";
.include "$CONFDIR/worker-normal.inc"
.include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/worker-normal.inc"
.include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/worker-normal.inc"
}
dmarc.conf: |-
actions = {
quarantine = "add_header";
reject = "reject";
}
redis.conf: |-
servers = "${var.instance}-${var.component}-redis.${var.namespace}.svc:6379";
db = "4";
EOF
}
resource "kubectl_manifest" "rspamd_service" {
yaml_body = <<-EOF
apiVersion: v1
kind: Service
metadata:
name: "${var.instance}-rspamd"
namespace: "${var.namespace}"
labels: ${jsonencode(local.rspamd-labels)}
spec:
type: ClusterIP
ports:
- port: 11333
targetPort: rspamd
protocol: TCP
name: rspamd
selector: ${jsonencode(local.rspamd-labels)}
EOF
}

40
share/wildduck/secret.tf Normal file
View File

@@ -0,0 +1,40 @@
resource "kubectl_manifest" "wildduck_secret" {
ignore_fields = ["metadata.annotations"]
yaml_body = <<-EOF
apiVersion: "secretgenerator.mittwald.de/v1alpha1"
kind: "StringSecret"
metadata:
name: "${var.instance}"
namespace: "${var.namespace}"
labels: ${jsonencode(local.common-labels)}
spec:
forceRegenerate: false
fields:
- fieldName: "srs"
length: "32"
- fieldName: "zonemta"
length: "32"
- fieldName: "webmail"
length: "32"
- fieldName: "totp"
length: "32"
- fieldName: "dkim"
length: "32"
EOF
}
data "kubernetes_secret_v1" "wildduck" {
depends_on = [ kubectl_manifest.wildduck_secret ]
metadata {
name = var.instance
namespace = var.namespace
}
}
locals {
secrets = {
srs = data.kubernetes_secret_v1.wildduck.data["srs"]
zonemta = data.kubernetes_secret_v1.wildduck.data["zonemta"]
webmail = data.kubernetes_secret_v1.wildduck.data["webmail"]
totp = data.kubernetes_secret_v1.wildduck.data["totp"]
dkim = data.kubernetes_secret_v1.wildduck.data["dkim"]
}
}

177
share/wildduck/webmail.tf Normal file
View File

@@ -0,0 +1,177 @@
locals {
webmail-labels = merge(local.common-labels, {
"app.kubernetes.io/component" = "webmail"
})
}
resource "kubectl_manifest" "webmail_deploy" {
yaml_body = <<-EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: "${var.instance}-webmail"
namespace: "${var.namespace}"
labels: ${jsonencode(local.webmail-labels)}
spec:
replicas: 1
selector:
matchLabels: ${jsonencode(local.webmail-labels)}
template:
metadata:
labels: ${jsonencode(local.webmail-labels)}
spec:
securityContext:
fsGroup: 1000
containers:
- name: webmail
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
image: "${var.images.webmail.registry}/${var.images.webmail.repository}:${var.images.webmail.tag}"
imagePullPolicy: "${var.images.webmail.pullPolicy}"
args:
- "--config=./config/webmail.toml"
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
scheme: HTTP
readinessProbe:
httpGet:
path: /
port: http
scheme: HTTP
resources:
{}
volumeMounts:
- name: config
mountPath: /app/config/webmail.toml
subPath: webmail.toml
volumes:
- name: config
configMap:
name: "${var.instance}-webmail"
EOF
}
resource "kubectl_manifest" "webmail_config" {
yaml_body = <<-EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: "${var.instance}-webmail"
namespace: "${var.namespace}"
labels: ${jsonencode(local.webmail-labels)}
data:
webmail.toml: |-
name="Wild Duck Mail"
title="wildduck-www"
[service]
# email domain for new users
domain="${var.domain-name}"
# default quotas for new users
quota=1024
recipients=2000
forwards=2000
identities=10
allowIdentityEdit=true
allowJoin=true
enableSpecial=false # if true the allow creating addresses with special usernames
# allowed domains for new addresses
domains=["${var.domain-name}"]
generalNotification="" # static notification to show on top of the page
[service.sso.http]
enabled = false
header = "X-UserName" # value from this header is treated as logged in username
authRedirect = "http:/127.0.0.1:3000/login" # URL to redirect non-authenticated users
logoutRedirect = "http:/127.0.0.1:3000/logout" # URL to redirect when user clicks on "log out"
[api]
url="http://wildduck.vynil-mail.svc.cluster.local:80"
accessToken="wildduck1234"
[dbs]
# mongodb connection string for the main database
mongo="mongodb://${var.component}:${local.mongo-password}@${var.instance}-${var.component}-mongo-svc.${var.namespace}.svc:27017/wildduck-webmail"
# redis connection string for Express sessions
redis="redis://${var.instance}-${var.component}-redis.${var.namespace}.svc:6379/5"
[www]
host=false
port=80
proxy=false
postsize="5MB"
log="dev"
secret="${local.secrets.webmail}"
secure=false
listSize=20
[recaptcha]
enabled=false
siteKey=""
secretKey=""
[totp]
# Issuer name for TOTP, defaults to config.name
issuer=false
# once setup do not change as it would invalidate all existing 2fa sessions
secret="${local.secrets.totp}"
[u2f]
# set to false if not using HTTPS
enabled=true
# must be https url or use default
appId="https://${var.domain-name}"
[log]
level="silly"
mail=true
[setup]
# these values are shown in the configuration help page
[setup.imap]
hostname="${var.sub-domain}.${var.domain-name}"
secure=true
port=143
[setup.pop3]
hostname="${var.sub-domain}.${var.domain-name}"
secure=true
port=110
[setup.smtp]
hostname="${var.sub-domain}.${var.domain-name}"
secure=true
port=25
EOF
}
resource "kubectl_manifest" "webmail_service" {
yaml_body = <<-EOF
apiVersion: v1
kind: Service
metadata:
name: "${var.instance}-webmail"
namespace: "${var.namespace}"
labels: ${jsonencode(local.webmail-labels)}
spec:
type: ClusterIP
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector: ${jsonencode(local.webmail-labels)}
EOF
}

402
share/wildduck/wildduck.tf Normal file
View File

@@ -0,0 +1,402 @@
locals {
wildduck-labels = merge(local.common-labels, {
"app.kubernetes.io/component" = "wildduck"
})
}
resource "kubectl_manifest" "wildduck_deploy" {
yaml_body = <<-EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: "${var.instance}-wildduck"
namespace: "${var.namespace}"
labels: ${jsonencode(local.wildduck-labels)}
spec:
replicas: 1
selector:
matchLabels: ${jsonencode(local.wildduck-labels)}
template:
metadata:
labels: ${jsonencode(local.wildduck-labels)}
spec:
securityContext:
fsGroup: 1000
containers:
- name: wildduck
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
image: "${var.images.wildduck.registry}/${var.images.wildduck.repository}:${var.images.wildduck.tag}"
imagePullPolicy: "${var.images.wildduck.pullPolicy}"
ports:
- name: http
containerPort: 8000
protocol: TCP
- name: imap
containerPort: 1430
protocol: TCP
- name: pop3
containerPort: 1100
protocol: TCP
livenessProbe:
httpGet:
path: /users
port: http
scheme: HTTP
httpHeaders:
- name: X-Access-Token
value: wildduck1234
readinessProbe:
httpGet:
path: /users
port: http
scheme: HTTP
httpHeaders:
- name: X-Access-Token
value: wildduck1234
resources:
{}
volumeMounts:
- name: wildduck-config-volume
mountPath: /wildduck/config
volumes:
- name: config
configMap:
name: "${var.instance}-wildduck"
EOF
}
resource "kubectl_manifest" "wildduck_config" {
yaml_body = <<-EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: "${var.instance}-wildduck"
namespace: "${var.namespace}"
labels: ${jsonencode(local.wildduck-labels)}
data:
default.toml: |-
# Uncomment if you start the app as root and want to downgrade
# once all privileged actions are completed
# If you do not use privileged ports then you can start the app already under required user account
#user="wildduck"
#group="wildduck"
# process title
ident="wildduck"
# how many processes to start
processes=1
# default quota storage in MB (can be overriden per user)
maxStorage=1024
# default smtp recipients for 24h (can be overriden per user)
maxRecipients=2000
# default forwarded messages for 24h (can be overriden per user)
maxForwards=2000
# If usernames are not email addresses then use this domain as hostname part
#emailDomain="mydomain.info"
[dbs]
# @include "dbs.toml"
[totp]
# If enabled then encrypt TOTP seed tokens with the secret password. By default TOTP seeds
# are not encrypted and stored as cleartext. Once set up do not change these values,
# otherwise decrypting totp seeds is going to fail
cipher="aes192"
secret="${local.secrets.totp}"
[u2f]
# Fully qualified URL of your website (must use HTTPS!)
appId="https://localhost:3000"
[attachments]
# @include "attachments.toml"
[log]
level="debug"
skipFetchLog=false # if true, then does not output individual * FETCH responses to log
# delete authentication log entries after 30 days
# changing this value only affects new entries
# set to false to not log authentication events
# set to 0 to keep the logs infinitely
authlogExpireDays=30
[log.gelf]
enabled=false
hostname=false # defaults to os.hostname()
component="wildduck"
[log.gelf.options]
graylogPort=12201
graylogHostname="127.0.0.1"
connection="lan"
[imap]
# @include "imap.toml"
[tls]
# @include "tls.toml"
[lmtp]
# @include "lmtp.toml"
[pop3]
# @include "pop3.toml"
[api]
# @include "api.toml"
[sender]
# @include "sender.toml"
[dkim]
# @include "dkim.toml"
[acme]
# @include "acme.toml"
[plugins]
# @include "plugins/*.toml"
[tasks]
# if enabled then process jobs like deleting expired messages etc
enabled=true
[smtp.setup]
# Public configuration for SMTP MDA, needed for mobileconfig files
hostname="${var.sub-domain}.${var.domain-name}"
secure=true
port=465
[webhooks]
# At least one server must have webhook processing enabled,
# otherwise events would pile up in the Redis queue.
enabled = true
api.toml: |-
enabled=true
port=8000
# by default bind to localhost only
host="0.0.0.0"
# Use `true` (HTTPS) for port 443 and `false` (HTTP) for 80
secure=false
# If set requires all API calls to have accessToken query argument with that value
# http://localhost:8080/users?accessToken=somesecretvalue
accessToken="wildduck1234"
[accessControl]
# If true then require a valid access token to perform API calls
# If a client provides a token then it is validated even if using a token is not required
enabled=false
# Secret for HMAC
# Changing this value invalidates all tokens
secret="a secret cat"
# Generated access token TTL in seconds. Token TTL gets extended by this value every time the token is used. Defaults to 14 days
#tokenTTL=1209600
# Generated access token max lifetime in seconds. Defaults to 180 days
#tokenLifetime=15552000
[roles]
# @include "roles.json"
[tls]
# If certificate path is not defined, use global or built-in self-signed certs
#key="/path/to/server/key.pem"
#cert="/path/to/server/cert.pem"
[mobileconfig]
# plist configuration for OSX/iOS profile files that are generated with Application Specific Passwords
# Use {email} in the description strings to replace it with account email address
# A reverse-DNS style identifier (com.example.myprofile, for example) that identifies the profile.
# This string is used to determine whether a new profile should replace an existing one or should be added. Username is prepended to this value.
identifier="com.email.wildduck"
# A human-readable name for the profile. This value is displayed on the Detail screen. It does not have to be unique.
displayName="WildDuck Mail"
# A human-readable string containing the name of the organization that provided the profile.
organization="WildDuck Mail Services"
# A description of the profile, shown on the Detail screen for the profile. This should be descriptive enough to help the user decide whether to install the profile.
displayDescription="Install this profile to setup {email}"
# A user-visible description of the email account, shown in the Mail and Settings applications.
accountDescription="WildDuck ({email})"
[mobileconfig.tls]
# If certificate path is not defined, use global or built-in self-signed certs
#key="/path/to/server/key.pem"
#cert="/path/to/server/cert.pem"
[cors]
origins = ["*"]
dbs.toml: |-
# mongodb connection string for the main database
mongo="mongodb://${var.component}:${local.mongo-password}@${var.instance}-${var.component}-mongo-svc.${var.namespace}.svc:27017/wildduck"
# redis connection string to connect to a single master (see below for Sentinel example)
redis="redis://${var.instance}-${var.component}-redis.${var.namespace}.svc:6379/3"
# WildDuck allows using different kind of data in different databases
# If you do not provide a database config value, then main database connection
# is used for everything
# You can either use a database name (uses shared connection) or a configutaion
# url (creates a separate connection) for each databases
# Optional database name or connection url for GridFS if you do not want to
# use the main db for storing attachments. Useful if you want
# to use a different mount folder or storage engine
#gridfs="wildduck"
# Optional database name or connection url for users collection if you do not want to
# use the main db for storing user/address data. Useful if you want
# to use a different mount folder or storage engine
#users="wildduck"
# Optional database name or connection url for ZoneMTA queue database. This is
# used to push outbound emails to the sending queue
sender="zone-mta"
#queued="mail"
dkim.toml: |-
# If enabled then encrypt DKIM keys with the secret password. By default DKIM keys
# are not encrypted and stored as cleartext. Once set up do not change these values,
# otherwise decrypting DKIM keys is going to fail
cipher="aes192"
secret="${local.secrets.dkim}"
# If true then spwans openssl command line executable for generating DKIM keys
# Otherwise forge library is used which is cross-environment but slower
useOpenSSL=true
# Define path to openssl if not in default path
#pathOpenSSL="/usr/local/bin/openssl"
# If true then also adds a signature for the outbound domain
# Affects WildDuck ZoneMTA plugin only
signTransportDomain=false
# do not change this
hashAlgo="sha256"
imap.toml: |-
# If enabled then WildDuck exposes an IMAP interface for listing and fetching emails
enabled=true
port=1430
host="0.0.0.0"
# Use `true` for port 993 and `false` for 143. If connection is not secured
# on connection then WildDuck enables STARTTLS extension
secure=false
# Max size for messages uploaded via APPEND
maxMB=25
# delete messages from \Trash and \Junk after retention days
retention=30
# Default max donwload bandwith per day in megabytes
maxDownloadMB=10000
# Default max upload bandwith per day in megabytes
maxUploadMB=10000
# Default max concurrent connections per service per client
maxConnections=15
# if `true` then do not autodelete expired messages
disableRetention=false
# If true, then disables STARTTLS support
disableSTARTTLS=true
# If true, then advertises COMPRESS=DEFLATE capability
enableCompression=false
# If true, then expect HAProxy PROXY header as the first line of data
useProxy=false
# useProxy=true # expect PROXY from all conections
# useProxy=['*'] # expect PROXY from all conections
# useProxy=['1.2.3.4', '1.2.3.5'] # expect PROXY only from connections from listed IP addresses
# an array of IP addresses to ignore (not logged)
ignoredHosts=[]
#name="WildDuck IMAP"
#version="1.0.0"
#vendor="WildDuck"
# Add extra IMAP interfaces
#[[interface]]
#enabled=true
#port=9143
#host="0.0.0.0"
#secure=false
#ignoreSTARTTLS=true
# If true then EXPUNGE is called after a message gets a \Deleted flag set
autoExpunge=true
[setup]
# Public configuration for IMAP
hostname="${var.sub-domain}.${var.domain-name}"
secure=true
# port defaults to imap.port
port=9930
[tls]
## If certificate path is not defined, use global or built-in self-signed certs
#key="/path/to/server/key.pem"
#cert="/path/to/server/cert.pem"
## You can also define extra options for specific TLS settings:
#ciphers="ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"
#secureProtocol="SSLv23_server_method"
## constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_TLSv1 => 100663296
#secureOptions=100663296
#[[interface]]
#enabled=true
#port=9143
#host="0.0.0.0"
#secure=false
#ignoreSTARTTLS=false
pop3.toml: |-
# If enabled then WildDuck exposes a limited POP3 interface for listing and fetching emails
enabled=true
port=1100
# by default bind to localhost only
host="0.0.0.0"
# If true, then disables STARTTLS support
disableSTARTTLS=true
# Use `true` for port 995 and `false` for 110
secure=false
# If true, then do not show server info in CAPA response
disableVersionString=false
# How many latest messages to list for LIST and UIDL
# POP3 server never lists all messages but only a limited length list
maxMessages=250
# Max donwload bandwith per day in megabytes
maxDownloadMB=10000
# If true, then expect HAProxy PROXY header as the first line of data
useProxy=false
# an array of IP addresses to ignore (not logged)
ignoredHosts=[]
#name="WildDuck POP3"
#version="1.0.0"
[tls]
# If certificate path is not defined, use global or built-in self-signed certs
#key="/path/to/server/key.pem"
#cert="/path/to/server/cert.pem"
[setup]
# Public configuration for POP3
hostname="${var.sub-domain}.${var.domain-name}"
secure=true
# port defaults to pop3.port
port=995
sender.toml: |-
# which ZoneMTA queue to use by default
zone="default"
# Collection name for GridFS storage
gfs="mail"
# Collection name for the queue
# see [dbs].sender option for choosing correct database to use for ZoneMTA queues
# by default the main wildduck database is used
collection="zone-queue"
# Hashing secret for loop detection
# Must be shared with haraka-plugin-wildduck
# If not set then looping is not tracked
loopSecret="${local.secrets.srs}"
EOF
}
resource "kubectl_manifest" "wildduck_service_api" {
yaml_body = <<-EOF
apiVersion: v1
kind: Service
metadata:
name: "${var.instance}-wildduck-api"
namespace: "${var.namespace}"
labels: ${jsonencode(local.wildduck-labels)}
spec:
type: ClusterIP
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector: ${jsonencode(local.wildduck-labels)}
EOF
}
resource "kubectl_manifest" "wildduck_service" {
yaml_body = <<-EOF
apiVersion: v1
kind: Service
metadata:
name: "${var.instance}-wildduck-mail"
namespace: "${var.namespace}"
labels: ${jsonencode(local.wildduck-labels)}
spec:
type: LoadBalancer
ports:
- port: 143
targetPort: imap
protocol: TCP
name: imap
- port: 110
targetPort: pop3
protocol: TCP
name: pop3
selector: ${jsonencode(local.wildduck-labels)}
EOF
}

197
share/wildduck/zonemta.tf Normal file
View File

@@ -0,0 +1,197 @@
locals {
zonemta-labels = merge(local.common-labels, {
"app.kubernetes.io/component" = "zonemta"
})
}
resource "kubectl_manifest" "zonemta_deploy" {
yaml_body = <<-EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: "${var.instance}-zonemta"
namespace: "${var.namespace}"
labels: ${jsonencode(local.zonemta-labels)}
spec:
replicas: 1
selector:
matchLabels: ${jsonencode(local.zonemta-labels)}
template:
metadata:
labels: ${jsonencode(local.zonemta-labels)}
spec:
securityContext:
fsGroup: 1000
containers:
- name: wildduck
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
image: "${var.images.zonemta.registry}/${var.images.zonemta.repository}:${var.images.zonemta.tag}"
imagePullPolicy: "${var.images.zonemta.pullPolicy}"
ports:
- name: smtp
containerPort: 5870
protocol: TCP
livenessProbe:
tcpSocket:
port: smtp
initialDelaySeconds: 20
periodSeconds: 30
readinessProbe:
tcpSocket:
port: smtp
initialDelaySeconds: 20
periodSeconds: 30
resources:
{}
volumeMounts:
- name: tls
mountPath: "/var/opt/certs"
readOnly: true
- name: config
mountPath: /app/config
- name: wildduck-zonemta-interfaces-config-volume
mountPath: /app/config/interfaces
- name: wildduck-zonemta-plugins-config-volume
mountPath: /app/config/plugins
- name: wildduck-zonemta-zones-config-volume
mountPath: /app/config/zones
volumes:
- name: config
configMap:
name: "${var.instance}-zonemta"
- name: tls
secret:
secretName: "${var.instance}-cert"
EOF
}
resource "kubectl_manifest" "zonemta_config" {
yaml_body = <<-EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: "${var.instance}-zonemta"
namespace: "${var.namespace}"
labels: ${jsonencode(local.zonemta-labels)}
data:
feeder.toml: |-
# Default SMTP interface for accepting mail for delivery
[feeder]
enabled=true
# How many worker processes to spawn
processes=1
# Maximum allowed message size 30MB
maxSize=31457280
# Local IP and port to bind to
host="0.0.0.0"
port=5870
# Set to true to require authentication
# If authentication is enabled then you need to use a plugin with an authentication hook
authentication=true
# How many recipients to allow per message
maxRecipients=1000
# Set to true to enable STARTTLS. Do not forget to change default TLS keys
starttls=true
# set to true to start in TLS mode if using port 465
# this probably does not work as TLS support with 465 in ZoneMTA is a bit buggy
secure=false
# define keys for STARTTLS/TLS. These paths are relative to CWD
# NB! Keys must be accessible by process user or SMTP authentication will fail.
key="/var/opt/certs/tls.key"
cert="/var/opt/certs/tls.crt"
dbs-production.toml: |-
# Database configuration
# this file is loaded when NODE_ENV=production
# MongoDB connection string
mongo="mongodb://${var.component}:${local.mongo-password}@${var.instance}-${var.component}-mongo-svc.${var.namespace}.svc:27017/wildduck"
# Redis connection string
redis="redis://${var.instance}-${var.component}-redis.${var.namespace}.svc:6379/2"
# Database name for ZoneMTA data in MongoDB. In most cases it should be the same as in the connection string
sender="wildduck"
# Database name for Wild Duck users
# users="wildduck"
# Database name for Wild Duck attachments
# gridfs="wildduck"
pools.toml: |-
# List local IP addresses that can be used for outbound tcp connections
# Server process must be able to locally bind to these addresses
[[default]]
address="0.0.0.0"
name="${var.sub-domain}.${var.domain-name}"
#
#[[default]]
#address="1.2.3.5"
#name="ip-2.hostname"
loop-breaker.toml: |-
["modules/zonemta-loop-breaker"]
enabled="sender"
secret="${local.secrets.zonemta}"
algo="md5"
wildduck.toml: |-
["modules/zonemta-wildduck"]
enabled=["receiver", "sender"]
# to which SMTP interfaces this plugin applies to. Use "*" for all interfaces
interfaces=["feeder"]
# optional hostname to be used in headers
# defaults to os.hostname()
hostname="${var.sub-domain}.${var.domain-name}"
# How long to keep auth records in log
authlogExpireDays=30
# default smtp recipients for 24h (can be overriden per user)
maxRecipients=2000
disableUploads=false # if true then messages are not uploaded to Sent Mail folder
uploadAll=false # if false then messages from Outlook are not uploaded to Sent Mail folder
# SRS settings for forwarded emails
# ---------------------------------
["modules/zonemta-wildduck".srs]
# Handle rewriting of forwarded emails. If false then SRS is not used
# Only affect messages that have interface set to "forwarder"
enabled=true
# SRS secret value. Must be the same as in the MX side
secret="${local.secrets.srs}"
# SRS domain, must resolve back to MX
rewriteDomain="${var.domain-name}"
# DKIM Settings
# -------------
["modules/zonemta-wildduck".dkim]
# If true then also adds a signature for the outbound domain
signTransportDomain=false
# If set then decrypt encrypted DKIM keys using this password
#secret="a secret cat"
# Cipher to use to decrypt encrypted DKIM keys
#cipher="aes192"
["modules/zonemta-wildduck".gelf]
enabled=false
component="mta"
["modules/zonemta-wildduck".gelf.options]
graylogPort=12201
graylogHostname='127.0.0.1'
connection='lan'
EOF
}
resource "kubectl_manifest" "zonemta_service" {
yaml_body = <<-EOF
apiVersion: v1
kind: Service
metadata:
name: "${var.instance}-zonemta"
namespace: "${var.namespace}"
labels: ${jsonencode(local.zonemta-labels)}
spec:
type: LoadBalancer
ports:
- port: 587
targetPort: smtp
protocol: TCP
name: smtp
selector: ${jsonencode(local.zonemta-labels)}
EOF
}