diff --git a/apps/base/artifactapi/resources/conf.d/remote-docker.yaml b/apps/base/artifactapi/resources/conf.d/remote-docker.yaml index 8eed39c..541b00b 100644 --- a/apps/base/artifactapi/resources/conf.d/remote-docker.yaml +++ b/apps/base/artifactapi/resources/conf.d/remote-docker.yaml @@ -20,6 +20,7 @@ remotes: package: "docker" description: "Docker Hub registry" immutable_patterns: + - "^boky/postfix" - "^library/almalinux" - "^library/busybox" - "^library/debian" @@ -35,6 +36,7 @@ remotes: - "^hashicorp/vault" - "^jfrog/" - "^rancher/" + - "^rspamd/rspamd" - "^traefik/" - "^ubi9/ubi-minimal" - "^victoriametrics/" diff --git a/apps/base/postfix/certificate.yaml b/apps/base/postfix/certificate.yaml new file mode 100644 index 0000000..11c74f9 --- /dev/null +++ b/apps/base/postfix/certificate.yaml @@ -0,0 +1,21 @@ +--- +# TLS certificate for postfix SMTP - stored in Vault and synced via VaultStaticSecret +# The postfix-tls secret is managed via vaultstaticsecret.yaml (keys: tls.crt, tls.key) +# This Certificate resource issues the cert used by Stalwart for SMTP/IMAP TLS +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: postfix-smtp-tls + namespace: postfix +spec: + secretName: postfix-smtp-tls + issuerRef: + name: vault-issuer + kind: ClusterIssuer + commonName: mail.main.unkin.net + dnsNames: + - mail.main.unkin.net + - smtp-in.main.unkin.net + privateKey: + size: 4096 + algorithm: RSA diff --git a/apps/base/postfix/gateway.yaml b/apps/base/postfix/gateway.yaml new file mode 100644 index 0000000..e730bb0 --- /dev/null +++ b/apps/base/postfix/gateway.yaml @@ -0,0 +1,37 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + labels: + traefik.io/instance: internal + annotations: + cert-manager.io/cluster-issuer: vault-issuer + cert-manager.io/common-name: rspamd.k8s.syd1.au.unkin.net + cert-manager.io/private-key-size: "4096" + external-dns.alpha.kubernetes.io/hostname: rspamd.k8s.syd1.au.unkin.net + external-dns.alpha.kubernetes.io/target: 198.18.200.4 + name: rspamd + namespace: postfix +spec: + gatewayClassName: traefik-internal + listeners: + - allowedRoutes: + namespaces: + from: Same + hostname: rspamd.k8s.syd1.au.unkin.net + name: http + port: 80 + protocol: HTTP + - allowedRoutes: + namespaces: + from: Same + hostname: rspamd.k8s.syd1.au.unkin.net + name: https + port: 443 + protocol: HTTPS + tls: + certificateRefs: + - group: "" + kind: Secret + name: rspamd-tls + mode: Terminate diff --git a/apps/base/postfix/httproute.yaml b/apps/base/postfix/httproute.yaml new file mode 100644 index 0000000..abf0baa --- /dev/null +++ b/apps/base/postfix/httproute.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: rspamd + namespace: postfix +spec: + parentRefs: + - name: rspamd + namespace: postfix + hostnames: + - rspamd.k8s.syd1.au.unkin.net + rules: + - backendRefs: + - name: rspamd + port: 11334 diff --git a/apps/base/postfix/kustomization.yaml b/apps/base/postfix/kustomization.yaml new file mode 100644 index 0000000..3975147 --- /dev/null +++ b/apps/base/postfix/kustomization.yaml @@ -0,0 +1,35 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - certificate.yaml + - gateway.yaml + - httproute.yaml + - namespace.yaml + - postfix-deployment.yaml + - postfix-hpa.yaml + - rspamd-deployment.yaml + - rspamd-hpa.yaml + - services.yaml + - vaultauth.yaml + - vaultstaticsecret.yaml + +configMapGenerator: + - name: postfix-config + files: + - resources/postfix/custom.cf + options: + disableNameSuffixHash: true + - name: postfix-transport + files: + - transport=resources/postfix/transport + options: + disableNameSuffixHash: true + - name: rspamd-config + files: + - worker-proxy.inc=resources/rspamd/local.d/worker-proxy.inc + - dkim_signing.conf=resources/rspamd/local.d/dkim_signing.conf + - milter_headers.conf=resources/rspamd/local.d/milter_headers.conf + options: + disableNameSuffixHash: true diff --git a/apps/base/postfix/namespace.yaml b/apps/base/postfix/namespace.yaml new file mode 100644 index 0000000..f0575c5 --- /dev/null +++ b/apps/base/postfix/namespace.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: postfix diff --git a/apps/base/postfix/postfix-deployment.yaml b/apps/base/postfix/postfix-deployment.yaml new file mode 100644 index 0000000..9f41b8a --- /dev/null +++ b/apps/base/postfix/postfix-deployment.yaml @@ -0,0 +1,90 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postfix + namespace: postfix +spec: + selector: + matchLabels: + app: postfix + template: + metadata: + annotations: + reloader.stakater.com/auto: "true" + labels: + app: postfix + spec: + containers: + - name: postfix + image: boky/postfix:3.9.1 + ports: + - containerPort: 25 + name: smtp + protocol: TCP + - containerPort: 587 + name: submission + protocol: TCP + env: + - name: SERVER_HOSTNAME + value: "mail.main.unkin.net" + - name: MYORIGIN + value: "main.unkin.net" + - name: MYNETWORKS + value: "127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16" + - name: ALLOWED_SENDER_DOMAINS + value: "main.unkin.net unkin.net" + - name: POSTFIX_myhostname + value: "mail.main.unkin.net" + - name: POSTFIX_mydestination + value: "localhost" + - name: POSTFIX_smtp_tls_security_level + value: "may" + - name: POSTFIX_smtpd_tls_security_level + value: "may" + - name: POSTFIX_smtpd_tls_cert_file + value: "/etc/postfix/tls/tls.crt" + - name: POSTFIX_smtpd_tls_key_file + value: "/etc/postfix/tls/tls.key" + livenessProbe: + tcpSocket: + port: 25 + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + tcpSocket: + port: 25 + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: "1" + memory: 512Mi + volumeMounts: + - name: postfix-config + mountPath: /etc/postfix/custom.d + readOnly: true + - name: postfix-transport + mountPath: /etc/postfix/transport + subPath: transport + readOnly: true + - name: postfix-tls + mountPath: /etc/postfix/tls + readOnly: true + volumes: + - name: postfix-config + configMap: + name: postfix-config + - name: postfix-transport + configMap: + name: postfix-transport + - name: postfix-tls + secret: + secretName: postfix-smtp-tls diff --git a/apps/base/postfix/postfix-hpa.yaml b/apps/base/postfix/postfix-hpa.yaml new file mode 100644 index 0000000..da0d738 --- /dev/null +++ b/apps/base/postfix/postfix-hpa.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: postfix-hpa + namespace: postfix +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: postfix + minReplicas: 2 + maxReplicas: 6 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + behavior: + scaleUp: + stabilizationWindowSeconds: 0 + selectPolicy: Max + policies: + - type: Percent + value: 100 + periodSeconds: 60 + - type: Pods + value: 4 + periodSeconds: 30 + scaleDown: + stabilizationWindowSeconds: 300 + selectPolicy: Min + policies: + - type: Percent + value: 30 + periodSeconds: 60 diff --git a/apps/base/postfix/resources/postfix/custom.cf b/apps/base/postfix/resources/postfix/custom.cf new file mode 100644 index 0000000..2ea4623 --- /dev/null +++ b/apps/base/postfix/resources/postfix/custom.cf @@ -0,0 +1,30 @@ +# Relay domains - mail for these domains is forwarded to Stalwart +relay_domains = main.unkin.net unkin.net + +# Transport map (texthash: reads plain text without postmap) +transport_maps = texthash:/etc/postfix/transport + +# rspamd milter +smtpd_milters = inet:rspamd:11332 +non_smtpd_milters = inet:rspamd:11332 +milter_default_action = accept +milter_protocol = 6 +milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen} + +# Message size limit (50 MiB) +message_size_limit = 52428800 + +# TLS for inbound connections +smtpd_tls_cert_file = /etc/postfix/tls/tls.crt +smtpd_tls_key_file = /etc/postfix/tls/tls.key +smtpd_use_tls = yes +smtpd_tls_security_level = may +smtpd_tls_loglevel = 1 + +# TLS for outbound delivery +smtp_tls_security_level = may +smtp_tls_loglevel = 1 + +# Queue settings +maximal_queue_lifetime = 5d +bounce_queue_lifetime = 1d diff --git a/apps/base/postfix/resources/postfix/transport b/apps/base/postfix/resources/postfix/transport new file mode 100644 index 0000000..df78b5f --- /dev/null +++ b/apps/base/postfix/resources/postfix/transport @@ -0,0 +1,2 @@ +main.unkin.net smtp:[stalwart.stalwart.svc.cluster.local]:25 +unkin.net smtp:[stalwart.stalwart.svc.cluster.local]:25 diff --git a/apps/base/postfix/resources/rspamd/local.d/dkim_signing.conf b/apps/base/postfix/resources/rspamd/local.d/dkim_signing.conf new file mode 100644 index 0000000..4f90d72 --- /dev/null +++ b/apps/base/postfix/resources/rspamd/local.d/dkim_signing.conf @@ -0,0 +1,13 @@ +enabled = true; +selector = "mail"; + +domain { + main.unkin.net { + privkey = "/etc/rspamd/dkim/private_key"; + selector = "mail"; + } + unkin.net { + privkey = "/etc/rspamd/dkim/private_key"; + selector = "mail"; + } +} diff --git a/apps/base/postfix/resources/rspamd/local.d/milter_headers.conf b/apps/base/postfix/resources/rspamd/local.d/milter_headers.conf new file mode 100644 index 0000000..01b8162 --- /dev/null +++ b/apps/base/postfix/resources/rspamd/local.d/milter_headers.conf @@ -0,0 +1,2 @@ +extended_spam_headers = true; +use = ["x-spam-status", "x-spam-score", "authentication-results"]; diff --git a/apps/base/postfix/resources/rspamd/local.d/worker-proxy.inc b/apps/base/postfix/resources/rspamd/local.d/worker-proxy.inc new file mode 100644 index 0000000..d797213 --- /dev/null +++ b/apps/base/postfix/resources/rspamd/local.d/worker-proxy.inc @@ -0,0 +1,7 @@ +milter = yes; +bind_socket = "*:11332"; + +upstream "local" { + default = yes; + self_scan = yes; +} diff --git a/apps/base/postfix/rspamd-deployment.yaml b/apps/base/postfix/rspamd-deployment.yaml new file mode 100644 index 0000000..76eda38 --- /dev/null +++ b/apps/base/postfix/rspamd-deployment.yaml @@ -0,0 +1,75 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rspamd + namespace: postfix +spec: + selector: + matchLabels: + app: rspamd + template: + metadata: + annotations: + reloader.stakater.com/auto: "true" + labels: + app: rspamd + spec: + securityContext: + runAsUser: 11333 + runAsGroup: 11333 + fsGroup: 11333 + containers: + - name: rspamd + image: rspamd/rspamd:4.0.1 + ports: + - containerPort: 11332 + name: milter + protocol: TCP + - containerPort: 11333 + name: worker + protocol: TCP + - containerPort: 11334 + name: controller + protocol: TCP + livenessProbe: + httpGet: + path: /ping + port: 11334 + initialDelaySeconds: 15 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /ping + port: 11334 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: "1" + memory: 512Mi + volumeMounts: + - name: rspamd-config + mountPath: /etc/rspamd/local.d + readOnly: true + - name: dkim-keys + mountPath: /etc/rspamd/dkim + readOnly: true + - name: rspamd-data + mountPath: /var/lib/rspamd + volumes: + - name: rspamd-config + configMap: + name: rspamd-config + - name: dkim-keys + secret: + secretName: dkim-keys + - name: rspamd-data + emptyDir: {} diff --git a/apps/base/postfix/rspamd-hpa.yaml b/apps/base/postfix/rspamd-hpa.yaml new file mode 100644 index 0000000..d24cf0d --- /dev/null +++ b/apps/base/postfix/rspamd-hpa.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: rspamd-hpa + namespace: postfix +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: rspamd + minReplicas: 2 + maxReplicas: 6 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + behavior: + scaleUp: + stabilizationWindowSeconds: 0 + selectPolicy: Max + policies: + - type: Percent + value: 100 + periodSeconds: 30 + - type: Pods + value: 4 + periodSeconds: 30 + scaleDown: + stabilizationWindowSeconds: 300 + selectPolicy: Min + policies: + - type: Percent + value: 30 + periodSeconds: 60 diff --git a/apps/base/postfix/services.yaml b/apps/base/postfix/services.yaml new file mode 100644 index 0000000..0d8ffcb --- /dev/null +++ b/apps/base/postfix/services.yaml @@ -0,0 +1,61 @@ +--- +# Internal service for rspamd - used by postfix pods in the same namespace +apiVersion: v1 +kind: Service +metadata: + name: rspamd + namespace: postfix +spec: + selector: + app: rspamd + ports: + - name: milter + port: 11332 + targetPort: 11332 + protocol: TCP + - name: worker + port: 11333 + targetPort: 11333 + protocol: TCP + - name: controller + port: 11334 + targetPort: 11334 + protocol: TCP +--- +# Internal ClusterIP for postfix - used by stalwart for outbound relay +apiVersion: v1 +kind: Service +metadata: + name: postfix + namespace: postfix +spec: + selector: + app: postfix + ports: + - name: smtp + port: 25 + targetPort: 25 + protocol: TCP + - name: submission + port: 587 + targetPort: 587 + protocol: TCP +--- +# External LoadBalancer for inbound MX (internet → postfix) +apiVersion: v1 +kind: Service +metadata: + name: postfix-external + namespace: postfix + annotations: + external-dns.alpha.kubernetes.io/hostname: smtp-in.main.unkin.net +spec: + type: LoadBalancer + externalTrafficPolicy: Local + selector: + app: postfix + ports: + - name: smtp + port: 25 + targetPort: 25 + protocol: TCP diff --git a/apps/base/postfix/vaultauth.yaml b/apps/base/postfix/vaultauth.yaml new file mode 100644 index 0000000..de10f26 --- /dev/null +++ b/apps/base/postfix/vaultauth.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultAuth +metadata: + name: default + namespace: postfix +spec: + allowedNamespaces: + - postfix + kubernetes: + audiences: + - vault + role: default + serviceAccount: default + tokenExpirationSeconds: 600 + method: kubernetes + mount: k8s/au/syd1 + vaultConnectionRef: vso-system/default diff --git a/apps/base/postfix/vaultstaticsecret.yaml b/apps/base/postfix/vaultstaticsecret.yaml new file mode 100644 index 0000000..0819ccb --- /dev/null +++ b/apps/base/postfix/vaultstaticsecret.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultStaticSecret +metadata: + name: dkim-keys + namespace: postfix +spec: + destination: + create: true + name: dkim-keys + overwrite: true + hmacSecretData: true + mount: kv + path: kubernetes/namespace/postfix/default/dkim-keys + refreshAfter: 5m + type: kv-v2 + vaultAuthRef: default diff --git a/apps/overlays/au-syd1/postfix/kustomization.yaml b/apps/overlays/au-syd1/postfix/kustomization.yaml new file mode 100644 index 0000000..a853959 --- /dev/null +++ b/apps/overlays/au-syd1/postfix/kustomization.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../../base/postfix diff --git a/argocd/applicationsets/platform.yaml b/argocd/applicationsets/platform.yaml index ac5afac..3d5c3ce 100644 --- a/argocd/applicationsets/platform.yaml +++ b/argocd/applicationsets/platform.yaml @@ -21,6 +21,7 @@ spec: - path: apps/overlays/*/inteldeviceplugins-system - path: apps/overlays/*/jfrog - path: apps/overlays/*/node-feature-discovery + - path: apps/overlays/*/postfix - path: apps/overlays/*/puppet - path: apps/overlays/*/purelb - path: apps/overlays/*/reflector-system