На хабре уже были статьи по настройке почтового клиента Thunderbird, с подробным разбором его возможностей и деталей настроек, а поиск по сети выдаёт множество блогов с продублированной информацией о его базовых возможностях.
Каждый из авторов решал настройку почты своим путём, используя разные языки и подходы.
Моя цель - попытаться унифицировать это, избавиться от самописных скриптов, предоставив готовый сервер-шаблонизатор для выдачи настроек по запросу почтового клиента.
Обзор
Основой для TACS является файл описания schema.yaml
, в котором задаются основные параметры:
# Directory to search for templates
# The search is also performed in subdirectories, but without following symbolic links
# Required file extension: *.tmpl
#templateDir: templates
# Block for local processing of user logins.
# Executes first because it doesn't require external requests.
# If there is a match, subsequent stages are not checked
#local:
# Default fields and properties
#default:
# Name of the GO template (*.tmpl), with insert fields - {{define "templateName"}}
#template: default
# A key-value map (hash table) that is used to fill the template.
# Can be overridden at the user level.
# If the field is not present at the user level, it will be set to the specified value.
# The key is a variable declared in the template - {{ .variableName }}
# Value - what will be substituted for the key
#fields:
# cn: user
# mail: mail@example.com
# telephoneNumber:
# position: no_body
# company: "\"Example\""
# List of users to handle requests.
# Is a hash table of "username: params",
# so each iteration of the key overwrites the previous values
#list:
#root:
# template: root
# fields:
# company: ""
# mail: root@example.com
# cn: Administrator
# signatureIsHTML: true
# signature: 'Sincerely\nroot'
# Block for processing users from LDAP.
# If there is a match, subsequent stages are not checked
#ldap:
# A unique field that identifies the user, a filter compiled for this:
# (uid=username), example - (sAMAccountName=username)
#uid: sAMAccountName
# Ldap path where to start the search
#searchBase: OU=SystemUsers,OU=Corp,DC=corp,DC=domain,DC=com
# Additional filter for user search. Added to the filter with uid:
# (&(uid=username)filter)
# In this case, search only for enabled users:
#filter: (!(userAccountControl:1.2.840.113556.1.4.803:=2))(objectCategory=person)(objectClass=user)
# Search in subgroups?
# If true - adds the option ":1.2.840.113556.1.4.1941:" to the filter
#subgroups: true
# Should I generate a response for a user who is not a member of any declared group?
# In this case, a default section must be declared
#allowWithoutGroups: true
# A key-value map (hash table) that is used to fill the template.
# Can be overridden at the ldap group level.
# If the field is not present at the user level, it will be set to the specified value.
# The key is a variable declared in the template - {{ .variableName }}
# Value - fields taken from the user's LDAP profile.
# Note.
# If you prefix an LDAP field name with "raw:", the value will be queried raw and base64 encoded
#default:
# template: default
# fields:
# cn: cn
# mail: mail
# telephoneNumber: telephoneNumber
# position: position
# company: company
# photo: raw:jpegPhoto
#list:
# The list of groups by which the user's groups are matched.
# The check is performed one by one, and the first match
# interrupts further processing.
#- group: "CN=Domain Users,OU=SystemUsers,OU=Corp,DC=corp,DC=domain,DC=com"
# template: default
# fields:
# cn: cn
# mail: mail
# telephoneNumber: telephoneNumber
# position: position
# company: company
# photo: raw:jpegPhoto
Для выдачи требуемой конфигурации, составляются списки с привязкой к ним соответствующих шаблонов. Списков два:
Локальный - явно указывается логин пользователя, поля для заполнения шаблона и сам шаблон, который должен быть отдан. Все значения шаблона задаются в самой конфигурации и никаких запросов в иные системы выполняться не будет.
LDAP - указывается группа из ldap, в которой должен состоять пользователь, LDAP-поля пользователя из которых необходимо брать свойства для заполнения шаблона и соответствующий шаблон. Если логин не определен в локальном списке, будет выполнен LDAP запрос на поиск пользователя и его свойств.
Рассмотрим более подробно оба вида списков:
local
local:
default:
template: users
fields:
cn: user
mail: mail@example.com
telephoneNumber:
position: no_body
company: "\"Example\""
list:
local.default
- этот раздел используется как значения по умолчанию для всех участников списка, если на самом участнике списка, не переопределено иное.
local.default.template
- задаёт имя шаблона, для формирования ответа
local.default.fields
- карта соответствия Go-Template переменных и значений, которые должны быть подставлены вместо них. В примере выше видно, что если в шаблоне будут переменные
{{ .cn }}, {{ .mail }}, {{ .position }}
то они будут заменены указанными значениями:
user, mail@example.com, no_body
local.list
- карта со списком логинов, на которые будут отдаваться настройки клиента. Здесь можно переопределить значения по умолчанию, заданные в разделе local.default
с тем же синтаксисом, например:
local:
default:
template: default_users
fields:
cn: user
mail: mail@example.com
telephoneNumber:
position: no_body
company: "\"Example\""
list:
user1:
user2:
template: managers
user3:
template: admins
fields:
cn: Вася Пупкин
position: Сетевой администратор
local.list.user1
: шаблон-default_users, поля заполнены по умолчаниюlocal.list.user2
: шаблон-managers, поля всё те жеlocal.list.user3
: шаблон-admins, поля - индивидуальные для данного пользователя
LDAP
Если логин пользователя не найден в локальном списке, выполняется поиск в LDAP.
Настройка LDAP списка расширяет локальные списки:
ldap:
uid: sAMAccountName
usersSearchBase: OU=Users,DC=corp,DC=example,DC=com
groupsSearchBase: OU=Groups,DC=corp,DC=example,DC=com
filter: (!(userAccountControl:1.2.840.113556.1.4.803:=2))(objectCategory=person)(objectClass=user)
subgroups: true
allowWithoutGroups: true
default:
list:
ldap.uid
- уникальный идентификатор для поиска пользователя в LDAP.ldap.usersSearchBase
- начальный каталог, от которого выполнять поиск пользователей в LDAPldap.groupSearchBase
- начальный каталог, от которого выполнять поиск групп в LDAPldap.filter
- дополнительный фильтр при поиске пользователей. Базовый поиск выполняется по фильтру($UID=$username)
или(sAMAccountName=pupkin.v)
. При указании данного поля, он расширяется через аргумент И -(&($UID=$username)$AddFilter)
и получается, например:
(&(sAMAccountName=pupkin.v)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(objectCategory=person)(objectClass=user))
ldap.subgroups: [true|false]
- выполнять ли поиск в дочерних LDAP группах или нет. Например у вас пользователи находятся в общей группеDomain Peoples
, аtacs_default
- группа для выбора шаблона. Можно было быtacs_default
скриптом навесить на каждого пользователя:
# Условная структура LDAP
user1.memberOf:
- Domain Peoples
- tacs_default
user1.memberOf:
- Domain Peoples
- tacs_default
...
а можно повесить на
Domain Peoples
, чтобы через наследование её влияние распространялось ниже:
# Условная структура LDAP
# Было:
user1.memberOf:
- Domain Peoples
user2.memberOf:
- Domain Peoples
# Стало:
Domain Peoples.memberOf:
- tacs_default
При true
, к фильтру добавляется опция :1.2.840.113556.1.4.1941:
которая позволяет просмотреть все дочерние подгруппы и найти искомого пользователя, если тот присутствует в дереве дочерних групп.
ldap.allowWithoutGroups: [true|false]
- разрешить ли пользователей без группы. Если пользователь не найден в группах LDAP, следует ли применить к нему значения LDAP по умолчанию или выдать 404 ошибку.
ldap.default
- так же как и в локальном списке, если не переназначено на группе, применяются данные значения.
ldap.default.template
- шаблон по умолчанию для LDAP групп
ldap.default.fields
- карта полей вида Go-Template-переменная: ldap-поле-пользователя
. Если в локальном списке значение было просто значением, то в LDAP-списке, значения берутся из свойств объекта пользователя. Например:
default:
template: ldap-default
fields:
cn: cn
position: description
Имя шаблона по умолчанию -
ldap-default
Переменная шаблона
{{ .cn }}
- соответствует полюcn
из LDAPПеременная шаблона
{{ .position }}
- соответствует полюdesccription
из LDAP
ldap.default.fields.*: raw:*
- если перед именем LDAP-поля указать префикс raw:
тогда значение поля будет выгружено в сыром виде и закодировано в base64. Это используется для выгрузки фотографий из поля LDAP jpegPhoto
:
default:
...
fields:
photo: "raw:jpegPhoto"
Получив фото из LDAP, можно составить html-подпись для пользователя:
<img src="data:image/jpeg;base64,{{ .photo }}"/>
ldap.list
- список групп с привязкой к ним шаблонов и необязательной карты свойств
list:
- group: "CN=tacs-with-photo,OU=tacs,OU=Groups,DC=corp,DC=example,DC=com"
template: with-ldap-photo
fields:
cn: cn
mail: mail
position: position
company: company
photo: raw:jpegPhoto
address: physicalDeliveryOfficeName
telephoneNumber: telephoneNumber
ldap.list.[*].group
- полный DN-путь к группе, например: "CN=tacs-with-photo,OU=tacs,OU=Groups,DC=corp,DC=example,DC=com"
ldap.list.[*].template
- необязательное переопределение используемого шаблона для указанной группы
ldap.list.[*].fields
- карта необязательных переопределений полей, аналогично разделу ldap.default.fields
.
Шаблоны
Каталог для поиска шаблонов указывается в scheme.yaml
:
templateDir
- TACS проходит по всем подкаталогам, кроме символических ссылок, анализируя и загружая в память *.tmpl
файлы, которые являются go-template.
Объявление шаблона выполняется блоком
{{define "template_name"}}...{{end}}
Имя шаблона указывается в
scheme.yaml
, в свойствеtemplate
и используется при генерации страницы конфигурации:
local:
default:
template: template_name
...
list:
username:
template: template_name
...
ldap:
default:
template: template_name
...
list:
- group: ...
template: template_name
Шаблоны могут вызывать другие шаблоны:
{{define "temp1"}}
...
{{end}}
{{define "temp2"}}
...
{{end}}
{{define "temp3"}}
{{template "temp1"}}
{{template "temp2"}}
{{end}}
Имя шаблона должно быть уникальным по сравнению с остальными, иначе шаблоны могут переписать друг-друга.
В шаблоне можно использовать переменные для подстановки значений из
scheme.yaml
/LDAP:
# Все переменные хранятся в "точке":
{{define "default"}}
{{.var_name}}
{{end}}
Переменные объявляются в
scheme.yaml
, в свойствеfields
:
local:
default:
template_key: value
...
list:
username:
template_key: value
...
ldap:
default:
fields:
template_key: ldap_field_with_value
template_key: raw:ldap_field_with_binary_value
...
list:
- group: ...
fields:
template_key: ldap_field_with_value
Примеры
Примерная конфигурация приведена в репозитории.