Skip to content

k-web-s/go-dovecot-director

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

go-dovecot-director

A Dovecot Proxy helper to map users to different backends. This application monitors backends running in Kubernetes. Simply, ready PODs are considered as live backends. They are monitored through Kubernetes endpoints.

Mapping is stored in PostgreSQL.

Setting up

Postgresql

Just simple table is needed for keeping records of username -> backend mappings. Howewer, is is highly recommended to have a foreign key on field username to valid users. For example, with postfixadmin the schema would be:

CREATE TABLE mailbox_username_backend (
    username character varying(255) NOT NULL PRIMARY KEY REFERENCES mailbox(username) ON DELETE CASCADE,
    backend character varying(255) NOT NULL,
    last_ts timestamp with time zone NOT NULL
);

Kubernetes

go-dovecot-director will monitor a kubernetes service, technically an endpoint only. Thus, the needed RBAC rules are minimal:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: go-dovecot-director
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: go-dovecot-director
rules:
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get
      - list
      - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: go-dovecot-director
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: go-dovecot-director
subjects:
  - kind: ServiceAccount
    name: go-dovecot-director

Then, the deployment needs to be told about the service/endpoint to monitor, and needs access to a postgresql table described in postgresql. This example monitors dovecot-backend service in namespace mail.

---
apiVersion: v1
kind: Secret
metadata:
  name: mail-database-secrets
stringDatadata:
  DATABASE_HOST: postgresql.database
  DATABASE_NAME: postfixadmin
  DATABASE_PASSWORD: password
  DATABASE_PORT: "5432"
  DATABASE_USER: postfixadmin
type: Opaque
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-dovecot-director
spec:
  selector:
    matchLabels:
      app: go-dovecot-director
  template:
    metadata:
      labels:
        app: go-dovecot-director
    spec:
      containers:
        - env:
            - name: NAMESPACE
              value: mail
            - name: SERVICE
              value: dovecot-backend
          envFrom:
            - secretRef:
                name: mail-database-secrets
          image: ghcr.io/k-web-s/go-dovecot-director
          name: go-dovecot-director
          resources:
            requests:
              cpu: 10m
              memory: 16Mi
          securityContext:
            capabilities:
              drop:
                - ALL
            allowPrivilegeEscalation: false
      securityContext:
        runAsNonRoot: true
      serviceAccountName: go-dovecot-director
---
apiVersion: v1
kind: Service
metadata:
  name: go-dovecot-director
spec:
  ports:
    - name: http
      port: 8080
      protocol: TCP
      targetPort: 8080
  selector:
    app: go-dovecot-director

Dovecot

Dovecot 2.4.X

A frontend dovecot proxy should have the following configuration as its passdb and userdb driver:

passdb lua {
  lua_file = /etc/dovecot/proxy.lua
}

userdb lua {
  lua_file = /etc/dovecot/proxy.lua
}

The LUA script might look like:

--- simple module to proxy passdb and userdb requests to http
--- serializes request parameters as json, and returns the returned json
--- as a table

local json = require "cjson"

local url = "http://mail-dovecot-director:8080"
local http_client = nil

function script_init()
  http_client = dovecot.http.client {
    connect_timeout = "5 sec",
    request_max_attempts = 2,
  }

  return 0
end

local function proxy_lookup(uri, request)
    local http_request = http_client:request({ url = url .. uri, method = "POST" })
    http_request:set_payload(json.encode({user=request.user}))
    local http_response = http_request:submit()
    if http_response:status() ~= 200 then
        error("Invalid http status received")
    end
    local resp = json.decode(http_response:payload())
    return resp.code, resp.attributes
end

function auth_passdb_lookup(request)
  return proxy_lookup("/auth_passdb_lookup", request)
end

function auth_userdb_lookup(request)
  return proxy_lookup("/auth_userdb_lookup", request)
end

Dovecot 2.3.X

A frontend dovecot proxy should have the following configuration as its passdb and userdb driver:

passdb {
  driver = lua
  args = file=/etc/dovecot/proxy.lua blocking=yes
}

userdb {
  driver = lua
  args = file=/etc/dovecot/proxy.lua blocking=yes
}

The LUA script might look like:

--- simple module to proxy passdb and userdb requests to http
--- serializes request parameters as json, and returns the returned json
--- as a table

local json = require "cjson"

local url = "http://go-dovecot-director:8080"
local http_client = nil

function script_init()
  http_client = dovecot.http.client {
    timeout = 5000,
    max_attempts = 2,
  }

  return 0
end

local function proxy_lookup(uri, request)
    local http_request = http_client:request({ url = url .. uri, method = "POST" })
    http_request:set_payload(json.encode({user=request.user}))
    local http_response = http_request:submit()
    if http_response:status() ~= 200 then
        error("Invalid http status received")
    end
    local payload = http_response:payload()
    local resp = json.decode(payload)
    return resp.code, resp.attributes
end

function auth_passdb_lookup(request)
  return proxy_lookup("/auth_passdb_lookup", request)
end

function auth_userdb_lookup(request)
  return proxy_lookup("/auth_userdb_lookup", request)
end

Releases

No releases published

Packages