albertito / chasquid

SMTP (email) server with a focus on simplicity, security, and ease of operation [mirror]
https://blitiri.com.ar/p/chasquid/
Other
868 stars 56 forks source link

feat(smtpserv): add support for HAProxy Proxy protocol #15

Closed denysvitali closed 3 years ago

denysvitali commented 3 years ago

Add support for HAProxy's Proxy Protocol (opt-in).

By default Chasquid gets the remote IP address from the TCP connection to perform the SPF detection. Unfortunately when the SMTP server is behind a load balancer (e.g: Kubernetes' Load Balancer or a simple HA Proxy LB), the SPF check will fail because the Load Balancer IP address (e.g: 10.42.0.1 for Kubernetes kube-proxy) doesn't match the one specified in the SPF domain record.

For this reason with this PR HAProxy's Proxy Protocol support can be enabled using the proxy_protocol config element.

This will therefore start to read on every new connection the PROXY line:

PROXY TCP4 193.169.253.128 178.63.0.254 53572 25

and will then replace the sourceIp with the one provided by HAProxy, so that the SPF check can succeed (if the remote IP address matches the SPF record).

albertito commented 3 years ago

Hi Thank you for this patch!

I think implementing this seems totally reasonable, and much appreciate your patch for it.

I have a few suggestions/comments on the approach itself, I will leave some comments on the patch, but I'm also happy to just make the changes myself if you don't have time for it, totally up to you.

Thanks again!

denysvitali commented 3 years ago

I'm all ears for your suggestions, in the meantime this solution is being tested with my personal mail server (based on chasquid + dovecot), https://github.com/denysvitali/docker-chasquid

albertito commented 3 years ago

I ended up writing an experimental commit since as I was reviewing it, it wasn't clear how my suggestions were going to look, and didn't want to send you on a false path. Sorry for not doing a normal review of this, as I'd usually do, it got a bit quirky in the end.

What do you think about commit 4ad4c3d4bcdb, on the (temporary) branch haproxy?

It's a similar approach in principle to yours, but has a few key differences:

I also added a few tests since I don't have a haproxy setup at hand, but of course nothing replaces real world testing :)

Please let me know what you think!

Thanks!

denysvitali commented 3 years ago

Your version is definitely better and I really like it more over mine, plus it has unit tests and also an integration test with haproxy. I guess I can't say anything more: impressive.

Thank you!

albertito commented 3 years ago

Thanks a lot for your kind words!

As usual, I've merged the patch in the next branch, it will sit there for a little in case there are some last minute adjustments, and then I'll move it to master. It will be included in the next release, of course :)

Thanks again for your patch and help implementing this!

denysvitali commented 3 years ago

I'm actually testing it already on my mail server, and so far it seems to work perfectly (:

Thank you very much for this project and your improvement over my patch! ✌️

ThinkChaos commented 3 years ago

Sorry for posting on a resolved PR, I just wanted to note that with a Kubernetes LoadBalencer service you can use service.spec.externalTrafficPolicy: Local (docs) to preserve the packet's source IP. I found out about this feature this week-end for a different use-case, but thought I'd mention it here in case someone find this thread.

denysvitali commented 3 years ago

Thanks @ThinkChaos , but for me it doesn't work since I'm using MetalLB and I'm already forwarding the traffic to another service (Traefik) 👍

albertito commented 3 years ago

Thanks to both!

If you send me a bit more details (can be privately), I can try expand the documentation with your examples. It might take a bit of back and forth since I'm not familiar with Kubernetes LB/Metal LB though :)

ThinkChaos commented 3 years ago

My use case was for a totally separate project, I'm not using chasquid with k8s so I'm afraid I can't help put together a complete example. I just remembered this PR and thought it might be a step in the right direction for someone finding this later on, or an alternate solution for @denysvitali.

denysvitali commented 3 years ago

chasquid.dep.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: chasquid
  name: chasquid
  namespace: mail
spec:
  replicas: 2
  selector:
    matchLabels:
      app: chasquid
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: chasquid
    spec:
      initContainers:
        - name: volume-permissions
          image: busybox
          command: 
            - sh
            - -c
            - chown -R 1001:1001 /srv/mail/mailboxes
          volumeMounts:
            - mountPath: /srv/mail/mailboxes
              name: mail-data
              subPath: mailboxes
        - name: clamav-permissions
          image: busybox
          command: 
            - sh
            - -c
            - chown -R 100:101 /var/lib/clamav
          volumeMounts:
            - name: mail-data
              mountPath: /var/lib/clamav
              subPath: clamav
      containers:
      - image: dvitali/chasquid:0.0.0-17
        name: chasquid
        resources: 
          requests:
            cpu: 100m
            memory: 256Mi
          limits:
            cpu: 1
            memory: 2Gi
        env:
          - name: CSQ_HOSTNAME
            value: mail.ded2.denv.it
          - name: CSQ_DOVECOT_AUTH
            value: "true"
          - name: CSQ_MAIL_DELIVERY_AGENT_BIN
            value: /usr/libexec/dovecot/dovecot-lda
          - name: CSQ_MAIL_DELIVERY_AGENT_ARGS
            value: "-f,%from%,-d,%to%"
          - name: CSQ_MONITORING_ADDRESS
            value: ":1099"
          - name: CSQ_HAPROXY_INCOMING
            value: "true"
          - name: DKIM_SELECTOR
            value: dkim
          - name: DOVECOT_DB_HOST
            value: mysql.mail.svc.cluster.local
          - name: DOVECOT_DB_NAME
            value: mail
          - name: DOVECOT_DB_USER
            valueFrom:
              secretKeyRef:
                name: mysql-dovecot-ro
                key: username
          - name: DOVECOT_DB_PASSWORD
            valueFrom:
              secretKeyRef:
                name: mysql-dovecot-ro
                key: password
          - name: RSPAMD_CONNECTION
            value: rspamd.mail.svc.cluster.local:11334
          - name: RSPAMD_PASSWORD
            value: plaintext secret that I need to remove
        readinessProbe:
          tcpSocket:
            port: 25
          periodSeconds: 10
          failureThreshold: 5
        startupProbe:
          tcpSocket:
            port: 25
          periodSeconds: 30
          failureThreshold: 20

        volumeMounts:
          - name: mail-data
            mountPath: /etc/chasquid/certs
            subPath: chasquid-certs
          - name: mail-data
            mountPath: /etc/chasquid/domains
            subPath: chasquid-domains
          - name: mail-data
            mountPath: /var/lib/clamav
            subPath: clamav
          - name: ssl-cert
            mountPath: /etc/chasquid/certs/mail.ded2.denv.it
          - name: dovecot-conf
            mountPath: /etc/dovecot/conf.d/dovecot-debug.conf
            subPath: dovecot-debug.conf
          - name: dovecot-conf
            mountPath: /etc/dovecot/conf.d/dovecot-inbox.conf
            subPath: dovecot-inbox.conf
          - name: mail-data
            mountPath: /srv/mail/mailboxes
            subPath: mailboxes
      volumes:
        - name: ssl-cert
          secret: 
            secretName: mail-cert
            items: 
              - key: tls.crt
                path: ./fullchain.pem
              - key: tls.key
                path: ./privkey.pem
        - name: dovecot-conf
          configMap:
            name: dovecot-additional-config
        - name: mail-data
          persistentVolumeClaim:
            claimName: mail-data

status: {}

mail.svc.yml

apiVersion: v1
kind: Service
metadata:
  name: mail-svc
  namespace: mail
spec:
spec:
  ports:
  - name: smtp
    port: 25
    nodePort: 30025
  - name: smtps
    port: 587
    nodePort: 30587
  selector:
    app: chasquid
  type: NodePort
  externalTrafficPolicy: Cluster