diff --git a/infra/k8s/gitlab/tailscale-proxy.yaml b/infra/k8s/gitlab/tailscale-proxy.yaml index 893e885a1..f45b6fa80 100644 --- a/infra/k8s/gitlab/tailscale-proxy.yaml +++ b/infra/k8s/gitlab/tailscale-proxy.yaml @@ -86,6 +86,20 @@ spec: limits: cpu: 200m memory: 128Mi + - name: imap-proxy + image: alpine/socat:latest + args: + - "TCP-LISTEN:993,fork,reuseaddr" + - "TCP:stalwart.foxhunt.svc.cluster.local:993" + ports: + - containerPort: 993 + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 50m + memory: 32Mi - name: tailscale image: ghcr.io/tailscale/tailscale:latest env: @@ -257,6 +271,26 @@ data: } } + # Stalwart Admin — mail.fxhnt.ai + server { + listen 443 ssl; + server_name mail.fxhnt.ai; + + ssl_certificate /etc/nginx/certs/tls.crt; + ssl_certificate_key /etc/nginx/certs/tls.key; + ssl_protocols TLSv1.2 TLSv1.3; + + location / { + proxy_pass https://stalwart.foxhunt.svc.cluster.local:443; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_http_version 1.1; + proxy_ssl_verify off; + } + } + # Mattermost — chat.fxhnt.ai server { listen 443 ssl; diff --git a/infra/k8s/network-policies/stalwart.yaml b/infra/k8s/network-policies/stalwart.yaml new file mode 100644 index 000000000..88a224659 --- /dev/null +++ b/infra/k8s/network-policies/stalwart.yaml @@ -0,0 +1,45 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: stalwart + namespace: foxhunt + labels: + app.kubernetes.io/part-of: foxhunt +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: stalwart + policyTypes: + - Ingress + - Egress + ingress: + # SMTP from cluster (Mattermost internal relay) + - ports: + - port: 25 + protocol: TCP + from: + - podSelector: + matchLabels: + app.kubernetes.io/name: mattermost + # IMAPS from Tailscale proxy + - ports: + - port: 993 + protocol: TCP + # HTTPS admin panel from Tailscale proxy + - ports: + - port: 443 + protocol: TCP + egress: + # DNS + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + # No outbound SMTP — internal-only for now. + # To add SCW TEM relay later: + # - ports: + # - port: 465 + # protocol: TCP + # - port: 587 + # protocol: TCP diff --git a/infra/k8s/stalwart/stalwart.yaml b/infra/k8s/stalwart/stalwart.yaml new file mode 100644 index 000000000..3d35d0b82 --- /dev/null +++ b/infra/k8s/stalwart/stalwart.yaml @@ -0,0 +1,153 @@ +# Internal-only mail server. No public MX, no LoadBalancer. +# SMTP: cluster-internal (Mattermost relay) +# IMAP: via Tailscale proxy (port 993) +# HTTPS admin: via Tailscale proxy (port 443 → mail.fxhnt.ai) +# Outbound: not configured (add SCW TEM relay later if needed) +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: stalwart-data + namespace: foxhunt + labels: + app.kubernetes.io/name: stalwart + app.kubernetes.io/part-of: foxhunt +spec: + accessModes: [ReadWriteOnce] + storageClassName: scw-bssd + resources: + requests: + storage: 5Gi +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: stalwart-config + namespace: foxhunt + labels: + app.kubernetes.io/name: stalwart + app.kubernetes.io/part-of: foxhunt +data: + config.toml: | + [server] + hostname = "mail.fxhnt.ai" + + [server.listener.smtp] + bind = "[::]:25" + protocol = "smtp" + + [server.listener.imaptls] + bind = "[::]:993" + protocol = "imap" + tls.implicit = true + + [server.listener.https] + bind = "[::]:443" + protocol = "http" + tls.implicit = true + + [storage] + data = "rocksdb" + blob = "rocksdb" + fts = "rocksdb" + lookup = "rocksdb" + directory = "internal" + + [store.rocksdb] + type = "rocksdb" + path = "/opt/stalwart-mail/data" + + [directory.internal] + type = "internal" + store = "rocksdb" + + [certificate.default] + cert = "/opt/stalwart-mail/tls/tls.crt" + private-key = "/opt/stalwart-mail/tls/tls.key" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: stalwart + namespace: foxhunt + labels: + app.kubernetes.io/name: stalwart + app.kubernetes.io/part-of: foxhunt +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: stalwart + strategy: + type: Recreate # RWO PVC + template: + metadata: + labels: + app.kubernetes.io/name: stalwart + app.kubernetes.io/part-of: foxhunt + spec: + nodeSelector: + k8s.scaleway.com/pool-name: platform + containers: + - name: stalwart + image: stalwartlabs/mail-server:latest + ports: + - containerPort: 25 + name: smtp + - containerPort: 993 + name: imaps + - containerPort: 443 + name: https + volumeMounts: + - name: stalwart-data + mountPath: /opt/stalwart-mail/data + - name: stalwart-config + mountPath: /opt/stalwart-mail/etc/config.toml + subPath: config.toml + - name: stalwart-tls + mountPath: /opt/stalwart-mail/tls + readOnly: true + readinessProbe: + tcpSocket: + port: 25 + initialDelaySeconds: 10 + periodSeconds: 10 + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 256Mi + volumes: + - name: stalwart-data + persistentVolumeClaim: + claimName: stalwart-data + - name: stalwart-config + configMap: + name: stalwart-config + - name: stalwart-tls + secret: + secretName: stalwart-tls +--- +apiVersion: v1 +kind: Service +metadata: + name: stalwart + namespace: foxhunt + labels: + app.kubernetes.io/name: stalwart + app.kubernetes.io/part-of: foxhunt +spec: + selector: + app.kubernetes.io/name: stalwart + ports: + - port: 25 + targetPort: 25 + name: smtp + - port: 993 + targetPort: 993 + name: imaps + - port: 443 + targetPort: 443 + name: https