diff --git a/wildduck/certificate.yaml b/wildduck/certificate.yaml new file mode 100644 index 0000000..7d255d9 --- /dev/null +++ b/wildduck/certificate.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: mail +spec: + dnsNames: + - mail.k-space.ee + issuerRef: + kind: ClusterIssuer + name: default + secretName: wildduck-tls diff --git a/wildduck/dns.yaml b/wildduck/dns.yaml new file mode 100644 index 0000000..220099a --- /dev/null +++ b/wildduck/dns.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: externaldns.k8s.io/v1alpha1 +kind: DNSEndpoint +metadata: + name: wildduck-mx +spec: + endpoints: + - dnsName: k-space.ee + recordTTL: 300 + recordType: MX + targets: + - "10 mail.k-space.ee" + - dnsName: k-space.ee + recordTTL: 300 + recordType: TXT + targets: + - "v=spf1 mx include:servers.mcsv.net -all" diff --git a/wildduck/haraka.yaml b/wildduck/haraka.yaml new file mode 100644 index 0000000..a1f6555 --- /dev/null +++ b/wildduck/haraka.yaml @@ -0,0 +1,167 @@ +--- +apiVersion: codemowers.cloud/v1beta1 +kind: RedisClaim +metadata: + name: haraka +spec: + class: ephemeral + capacity: 100Mi +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: haraka +data: + loglevel: info + plugin_timeout: "180" + queue_dir: /var/lib/haraka/queue + plugins: |- + spf + clamd + rspamd + wildduck + rspamd.ini: |- + host = rspamd + port = 11333 + add_headers = always + timeout = 30 + [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 = / + clamd.ini: |- + clamd_socket = clamav:3310 + [reject] + virus=true + error=false + smtp.ini: |- + listen=0.0.0.0:2525 + nodes=1 + tls.ini: |- + key=/cert/tls.key + cert=/cert/tls.crt + wildduck.js: |- + module.exports = { + "redis": process.env.REDIS_URI, + "mongo": { + "url": process.env.MONGO_URI, + "sender": "application" + }, + "sender": { + "enabled": true, + "zone": "default", + "gfs": "mail", + "collection": "zone-queue" + }, + "srs": { + "secret": "foobar" + }, + "attachments": { + "type": "gridstore", + "bucket": "attachments", + "decodeBase64": true + }, + "log": { + "authlogExpireDays": 30 + }, + "limits": { + "windowSize": 3600, + "rcptIp": 100, + "rcptWindowSize": 60, + "rcpt": 60 + }, + "gelf": { + "enabled": false + }, + "rspamd": { + "forwardSkip": 10, + "blacklist": [ + "DMARC_POLICY_REJECT" + ], + "softlist": [ + "RBL_ZONE" + ], + "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" + } + } + } +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: haraka +spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/name: wildduck + app.kubernetes.io/component: haraka + template: + metadata: + labels: + app.kubernetes.io/name: wildduck + app.kubernetes.io/component: haraka + spec: + containers: + - name: haraka + image: docker.io/codemowers/wildduck-haraka-inbound:latest@sha256:a130cc6a60ab2a47cb5971355ed2474136254613b4b8bd30aeabc6e123695ea3 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 2525 + name: haraka-mta + securityContext: + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65534 + volumeMounts: + - name: wildduck-haraka-config + mountPath: /etc/haraka + readOnly: true + - name: wildduck-haraka-config + mountPath: /etc/haraka/config + readOnly: true + - name: var-lib-haraka + mountPath: /var/lib/haraka + - mountPath: /cert + name: cert + env: + - name: REDIS_URI + valueFrom: + secretKeyRef: + name: redis-wildduck-owner-secrets + key: REDIS_MASTER_0_URI + - name: MONGO_URI + valueFrom: + secretKeyRef: + name: wildduck + key: MONGO_URI + volumes: + - name: cert + secret: + secretName: wildduck-tls + - name: wildduck-haraka-config + projected: + sources: + - configMap: + name: haraka + - name: var-lib-haraka + emptyDir: + sizeLimit: 500Mi diff --git a/wildduck/loadbalancer.yaml b/wildduck/loadbalancer.yaml new file mode 100644 index 0000000..147ea0c --- /dev/null +++ b/wildduck/loadbalancer.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: wildduck + annotations: + external-dns.alpha.kubernetes.io/hostname: mail.k-space.ee + metallb.universe.tf/address-pool: wildduck +spec: + loadBalancerIP: 193.40.103.25 + type: LoadBalancer + externalTrafficPolicy: Local + selector: + app.kubernetes.io/name: wildduck + ports: + - port: 8080 + name: wildduck-api + targetPort: wildduck-api + - port: 993 + name: wildduck-mda + targetPort: wildduck-mda + - port: 465 + name: zonemta-msa + targetPort: zonemta-msa + - port: 25 + name: haraka-mta + targetPort: haraka-mta + diff --git a/wildduck/mongo.yaml b/wildduck/mongo.yaml new file mode 100644 index 0000000..f7c1985 --- /dev/null +++ b/wildduck/mongo.yaml @@ -0,0 +1,100 @@ +--- +apiVersion: codemowers.cloud/v1beta1 +kind: SecretClaim +metadata: + name: wildduck-readwrite-password +spec: + mapping: + - key: password + value: "%(plaintext)s" +--- +apiVersion: codemowers.cloud/v1beta1 +kind: SecretClaim +metadata: + name: wildduck-readonly-password +spec: + mapping: + - key: password + value: "%(plaintext)s" +--- +apiVersion: mongodbcommunity.mongodb.com/v1 +kind: MongoDBCommunity +metadata: + name: wildduck-mongodb +spec: + additionalMongodConfig: + systemLog: + quiet: true + members: 2 + arbiters: 1 + type: ReplicaSet + version: "6.0.3" + security: + authentication: + modes: ["SCRAM"] + users: + - name: readwrite + db: application + passwordSecretRef: + name: wildduck-readwrite-password + roles: + - name: readWrite + db: application + scramCredentialsSecretName: wildduck-readwrite + - name: readonly + db: application + passwordSecretRef: + name: wildduck-readonly-password + roles: + - name: read + db: application + scramCredentialsSecretName: wildduck-readonly + statefulSet: + spec: + logLevel: WARN + template: + spec: + containers: + - name: mongod + resources: + requests: + cpu: 100m + memory: 1Gi + limits: + cpu: 4000m + memory: 1Gi + - name: mongodb-agent + resources: + requests: + cpu: 1m + memory: 100Mi + limits: {} + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - wildduck-svc + topologyKey: topology.kubernetes.io/zone + volumeClaimTemplates: + - metadata: + name: logs-volume + spec: + storageClassName: mongo + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 512Mi + - metadata: + name: data-volume + spec: + storageClassName: mongo + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi diff --git a/wildduck/rspamd.yaml b/wildduck/rspamd.yaml index 29624f4..b84f29f 100644 --- a/wildduck/rspamd.yaml +++ b/wildduck/rspamd.yaml @@ -1,4 +1,15 @@ --- +apiVersion: v1 +kind: ConfigMap +metadata: + name: rspamd +data: + logging.inc: | + type = console; + level = "notice"; + worker-normal.inc: | + bind_socket = "*:11333"; +--- apiVersion: apps/v1 kind: Deployment metadata: @@ -20,9 +31,6 @@ spec: containers: - name: rspamd image: docker.io/codemowers/rspamd - command: - - /usr/bin/rspamd - - -f ports: - containerPort: 11333 name: rspamd @@ -38,6 +46,11 @@ spec: volumeMounts: - name: var-lib-rspamd mountPath: /var/lib/rspamd + - name: var-run + mountPath: /run/rspamd + - name: rspamd-config + mountPath: /etc/rspamd/local.d + readOnly: true volumes: - name: var-lib-rspamd emptyDir: @@ -48,6 +61,11 @@ spec: - name: var-lib-nginx-tmp emptyDir: medium: Memory + - name: rspamd-config + projected: + sources: + - configMap: + name: rspamd --- apiVersion: v1 kind: Service diff --git a/wildduck/webmail.yaml b/wildduck/webmail.yaml index bd0b6c2..074b49f 100644 --- a/wildduck/webmail.yaml +++ b/wildduck/webmail.yaml @@ -54,7 +54,7 @@ data: secure=true port=465 [api] - url="https://mail.k-space.ee" + url="http://wildduck-api:8080" --- apiVersion: apps/v1 kind: Deployment @@ -96,8 +96,8 @@ spec: - name: APPCONF_dbs_redis valueFrom: secretKeyRef: - name: redis-webmail-owner-secrets - key: REDIS_MASTER_0_URI + name: redis-wildduck-owner-secrets + key: REDIS_MASTER_1_URI volumes: - name: webmail-config projected: @@ -126,7 +126,7 @@ metadata: annotations: kubernetes.io/ingress.class: traefik traefik.ingress.kubernetes.io/router.entrypoints: websecure - traefik.ingress.kubernetes.io/router.middlewares: wildduck-webmail@kubernetescrd + traefik.ingress.kubernetes.io/router.middlewares: wildduck-webmail@kubernetescrd,wildduck-webmail-redirect@kubernetescrd traefik.ingress.kubernetes.io/router.tls: "true" external-dns.alpha.kubernetes.io/target: traefik.k-space.ee spec: @@ -144,3 +144,13 @@ spec: tls: - hosts: - "*.k-space.ee" +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: webmail-redirect +spec: + redirectRegex: + regex: ^https://webmail.k-space.ee/$ + replacement: https://webmail.k-space.ee/webmail/ + permanent: false diff --git a/wildduck/wildduck.yaml b/wildduck/wildduck.yaml new file mode 100644 index 0000000..9d1556d --- /dev/null +++ b/wildduck/wildduck.yaml @@ -0,0 +1,85 @@ +--- +apiVersion: codemowers.cloud/v1beta1 +kind: RedisClaim +metadata: + name: wildduck +spec: + class: ephemeral + capacity: 100Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: wildduck-api +spec: + selector: + app.kubernetes.io/name: wildduck + app.kubernetes.io/component: wildduck + ports: + - port: 8080 + name: wildduck-api +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: wildduck +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: wildduck + app.kubernetes.io/component: wildduck + template: + metadata: + labels: + app.kubernetes.io/name: wildduck + app.kubernetes.io/component: wildduck + spec: + containers: + - name: wildduck + image: docker.io/nodemailer/wildduck + ports: + - containerPort: 8080 + name: wildduck-api + - containerPort: 9993 + name: wildduck-mda + securityContext: + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + resources: + limits: + cpu: 500m + memory: 200Mi + requests: + cpu: 10m + memory: 100Mi + env: + - name: APPCONF_tls_key + value: /cert/tls.key + - name: APPCONF_tls_cert + value: /cert/tls.crt + - name: APPCONF_api_host + value: "0.0.0.0" + - name: APPCONF_api_accessToken + valueFrom: + secretKeyRef: + name: wildduck + key: WILDDUCK_API_TOKEN + - name: APPCONF_dbs_mongo + valueFrom: + secretKeyRef: + name: wildduck + key: MONGO_URI + - name: APPCONF_dbs_redis + valueFrom: + secretKeyRef: + name: redis-wildduck-owner-secrets + key: REDIS_MASTER_0_URI + volumeMounts: + - mountPath: /cert + name: cert + volumes: + - name: cert + secret: + secretName: wildduck-tls diff --git a/wildduck/zonemta.yaml b/wildduck/zonemta.yaml new file mode 100644 index 0000000..0b239fb --- /dev/null +++ b/wildduck/zonemta.yaml @@ -0,0 +1,72 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: zonemta +spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/name: wildduck + app.kubernetes.io/component: zonemta + template: + metadata: + labels: + app.kubernetes.io/name: wildduck + app.kubernetes.io/component: zonemta + spec: + containers: + - name: wildduck + image: docker.io/codemowers/wildduck-zonemta-outbound:latest@sha256:a35453409c29882bacb4a758909a38ed62daa875ad72cf706996bb144703ef49 + imagePullPolicy: IfNotPresent + command: + - /sbin/tini + - -- + - node + - index.js + ports: + - containerPort: 9465 + name: zonemta-msa + - containerPort: 10280 + name: api + securityContext: + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + resources: + limits: + cpu: 500m + memory: 1000Mi + requests: + cpu: 10m + memory: 500Mi + env: + - name: APPCONF_smtpInterfaces_feeder_key + value: /cert/tls.key + - name: APPCONF_smtpInterfaces_feeder_cert + value: /cert/tls.crt + - name: APPCONF_smtpInterfaces_feeder_port + value: "9465" + - name: APPCONF_smtpInterfaces_feeder_host + value: "0.0.0.0" + - name: APPCONF_smtpInterfaces_feeder_secure + value: "true" + - name: APPCONF_dbs_sender + value: zone-mta + - name: APPCONF_dbs_mongo + valueFrom: + secretKeyRef: + name: wildduck + key: MONGO_URI + - name: APPCONF_dbs_redis + valueFrom: + secretKeyRef: + name: redis-wildduck-owner-secrets + key: REDIS_MASTER_0_URI + volumeMounts: + - mountPath: /cert + name: cert + volumes: + - name: cert + secret: + secretName: wildduck-tls