Skip to content

The Best Kubernetes Setup Possible #2094

@georglauterbach

Description

@georglauterbach

Subject

I would like to reconfigure and document an already documented mail server use case.

Description

The current documentation on K8s (Kubenetes) is okay, and I was able to extract essential parts off of it. However, it lacked a bit of quality from my POV. I would like to re-do the docs on K8s. I am currently transitioning to K3s and I want to get the, especially security-wise, best "deployment" (this is a Kubernetes-native term, but I'm referring to Services, etc. too) possible. I'm using Kustomize for the whole setup, and I'd document this too once finished.

I'm linking the maintainers that I know are using K8s to get some help on the topic: @radicand
I'm also trying to get some feedback from the currently most active maintainers: @casperklein @wernerfred @polarathene
And, because in this case, more are better, I'll also link @NorseGaud, @fbartels and @reneploetz

Manifests

I'm posting my current manifest files, that is, the deployment and the service here. I'd like to get feedback on them. Moreover, I have some questions down below.

The deployment goes first. There are a lot of file mounts that may not all be necessary, but I will explain them below.

---
apiVersion: apps/v1
kind: Deployment

metadata:
  name: mailserver

  annotations:
    ignore-check.kube-linter.io/run-as-non-root: >-
      The mail server needs to run as root
    ignore-check.kube-linter.io/privileged-ports: >-
      The mail server needs privilegdes ports
    ignore-check.kube-linter.io/no-read-only-root-fs: >-
      There are too many files written to make The
      root FS read-only

spec:
  replicas: 1
  selector:
    matchLabels:
      app: mailserver

  template:
    metadata:
      labels:
        app: mailserver

      annotations:
        container.apparmor.security.beta.kubernetes.io/mailserver: runtime/default

    spec:
      hostname: mailserver
      containers:
        - name: mailserver
          image: ghcr.io/docker-mailserver/docker-mailserver:10.0.0
          imagePullPolicy: IfNotPresent

          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: false
            runAsUser: 0
            runAsGroup: 0
            runAsNonRoot: false
            privileged: false
            capabilities:
              add:
                # file permission capabilities
                - CHOWN
                - FOWNER
                - MKNOD
                - SETGID
                - SETUID
                - DAC_OVERRIDE
                # miscellaneous  capabilities
                - SYS_CHROOT
                - NET_BIND_SERVICE
                - KILL
              drop: [ALL]
            seccompProfile:
              type: RuntimeDefault

          resources:
            limits:
              memory: 4Gi
              cpu: 1500m
            requests:
              memory: 2Gi
              cpu: 600m

          volumeMounts:
            # OpenDKIM
            - name: opendkim-files
              subPath: mail_com.private
              mountPath: /tmp/docker-mailserver/opendkim/keys/domain1.tld/mail.private
              readOnly: true
            - name: opendkim-files
              subPath: mail_com.txt
              mountPath: /tmp/docker-mailserver/opendkim/keys/domain1.tld/mail.txt
              readOnly: true
            - name: opendkim-files
              subPath: mail_de.private
              mountPath: /tmp/docker-mailserver/opendkim/keys/domain2.tld/mail.private
              readOnly: true
            - name: opendkim-files
              subPath: mail_de.txt
              mountPath: /tmp/docker-mailserver/opendkim/keys/domain2.tld/mail.txt
              readOnly: true
            - name: opendkim-files
              subPath: KeyTable
              mountPath: /tmp/docker-mailserver/opendkim/KeyTable
              readOnly: true
            - name: opendkim-files
              subPath: SigningTable
              mountPath: /tmp/docker-mailserver/opendkim/SigningTable
              readOnly: true
            - name: opendkim-files
              subPath: TrustedHosts
              mountPath: /tmp/docker-mailserver/opendkim/TrustedHosts
              readOnly: true

            # other
            - name: files
              subPath: dh_4096.pem
              mountPath: /etc/postfix/dh_4096.pem
              readOnly: true
            - name: files
              subPath: hostname
              mountPath: /etc/hostname
              readOnly: true
            - name: files
              subPath: hosts
              mountPath: /etc/hosts
              readOnly: true

            # Postfix
            - name: files
              subPath: main.cf
              mountPath: /etc/postfix/main.cf
              readOnly: true
            - name: files
              subPath: postfix-accounts.cf
              mountPath: /tmp/docker-mailserver/postfix-accounts.cf
              readOnly: true
            - name: files
              subPath: postfix-virtual.cf
              mountPath: /tmp/docker-mailserver/postfix-virtual.cf
              readOnly: true

            # scrips
            - name: files
              subPath: setup-stack.sh
              mountPath: /usr/local/bin/setup-stack.sh
              readOnly: true
            - name: files
              subPath: user-patches.sh
              mountPath: /tmp/docker-mailserver/user-patches.sh
              readOnly: true

            # spam
            - name: files
              subPath: 50-user
              mountPath: /etc/amavis/conf.d/50-user
              readOnly: true
            - name: files
              subPath: header_checks.pcre
              mountPath: /etc/postfix/maps/header_checks.pcre
              readOnly: true
            - name: files
              subPath: postscreen.conf
              mountPath: /etc/fail2ban/filter.d/postscreen.conf
              readOnly: true
            - name: files
              subPath: user-jail.local
              mountPath: /etc/fail2ban/jail.d/user-jail.local
              readOnly: true

            # PVCs
            - name: data
              mountPath: /var/mail
              subPath: data
              readOnly: false
            - name: data
              mountPath: /var/mail-state
              subPath: state
              readOnly: false
            - name: data
              mountPath: /var/log/mail
              subPath: log
              readOnly: false

            # time
            - name: localtime
              mountPath: /etc/localtime
              readOnly: true
            - name: timezone
              mountPath: /etc/timezone
              readOnly: true

            # certificates
            - name: certificates-rsa
              mountPath: /secrets/ssl/rsa/
              readOnly: true
            - name: certificates-ecdsa
              mountPath: /secrets/ssl/ecdsa/
              readOnly: true

            # other
            - name: tmp-files
              mountPath: /tmp
              readOnly: false

          ports:
            - name: transfer
              containerPort: 25
              protocol: TCP
            - name: esmtp-implicit
              containerPort: 465
              protocol: TCP
            - name: esmtp-explicit
              containerPort: 587
            - name: imap-implicit
              containerPort: 993
              protocol: TCP

          envFrom:
            - configMapRef:
                name: mailserver.environment

      restartPolicy: Always

      volumes:
        # configuration files
        - name: files
          configMap:
            name: mailserver.other.files
        - name: opendkim-files
          configMap:
            name: mailserver.opendkim.files

        # PVCs
        - name: data
          persistentVolumeClaim:
            claimName: data

        # time
        - name: timezone
          hostPath:
            path: /etc/timezone
            type: File
        - name: localtime
          hostPath:
            path: /etc/localtime
            type: File

        # certificates
        - name: certificates-rsa
          secret:
            secretName: mail-tls-certificate-rsa
            items:
              - key: tls.key
                path: tls.key
              - key: tls.crt
                path: tls.crt
        - name: certificates-ecdsa
          secret:
            secretName: mail-tls-certificate-ecdsa
            items:
              - key: tls.key
                path: tls.key
              - key: tls.crt
                path: tls.crt

        # other
        - name: tmp-files
          emptyDir: {}

The service is more or less straight-forward:

---
apiVersion: v1
kind: Service

metadata:
  name: mailserver
  labels:
    app: mailserver

spec:
  type: LoadBalancer
  externalTrafficPolicy: Local

  selector:
    app: mailserver

  ports:
    # Transfer
    - name: transfer
      port: 25
      targetPort: transfer
      protocol: TCP
    # ESMTP with implicit TLS
    - name: esmtp-implicit
      port: 465
      targetPort: esmtp-implicit
      protocol: TCP
    # ESMTP with explicit TLS (STARTTLS)
    - name: esmtp-explicit
      port: 587
      targetPort: esmtp-explicit
      protocol: TCP
    # IMAPS with implicit TLS
    - name: imap-implicit
      port: 993
      targetPort: imap-implicit
      protocol: TCP

(Possible) Improvements

The goal is simple, achieving it non-trivial: Define a proper ´securityContext` in the deployment. This is, at least to me, critical in achieving the best security possible.

Especially getting

  1. readOnlyRootFilesystem: set to true
  2. runAsUser: to not be 0
  3. runAsGroup: to not be 0
  4. runAsNonRoot: set to true
  5. capabilities.drop set to [ALL] and only add the caps that we really need with capabilities.add

This will require

  1. All files that need to be adjusted on run-time to be mounted externally
  2. Have a user that can manage the server as if root but without being root
  3. Have a group that can manage the server as if root but without being root
  4. If 2. and 3. are fulfilled, this becomes true automatically
  5. Just a matter of knowing which capabilities we actually need...

Feedback very welcome! I will test the deployment tomorrow at the earliest because I've got a lot to do ATM, but I will report back. In the meantime, I'd be happy to hear your ideas :D

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions